Садржај
2 Класе и објекти
2.1 Основни појмови о класама и објектима
3 Генеричке класе
4 Наслеђивање и полиморфизам
5 Примери пројеката са решењима
5.1 Различита кретања
5.2 Квиз
5.4 Приказ рада алгоритама сортирања

Основно о наслеђивању

У овој лекцији:

  • који проблем решава наслеђивање,

  • како се користи наслеђивање,

  • наслеђивање као нова врста односа између класа.

Зашто нам је наслеђивање потребно

Замислимо следећу ситуацију. У оквиру већег пројекта негде већ постоји класа која нам делимично одговара, тако да бисмо је радо допунили неким методима који су нама потребни. При томе је могуће да нисмо једини који желе да допуне постојећу класу, али други програмери имају на уму другачије допуне те класе. Намеће се питање – да ли те допуне могу да се ускладе, односно да ли уопште постоји облик класе који би могао да одговара свима? Са друге стране, програмеру који је ту класу написао она одговара таква каква је. Њему би било која нова функционалност само непотребно повећала и закомпликовала класу и донекле успорила даљи развој. Најгоре од свега, мењање кода те класе би донело ризик да се постојећа функционалност поремети, тј. да се унесу багови. Због свега овога, мењање постојеће класе није свима прихватљиво решење.

Након одустајања од измене класе, следеће што може да нам падне на памет је да поменуту класу копирамо и преименујемо, а затим прилагодимо својим потребама. Нажалост, ту долази до делимичног дуплирања кода, што је лоше из више разлога, као што смо често истицали. Као прво, уз такав начин рада цео програм расте веома брзо и тиме постаје све непрегледнији и тежи за сналажење. Као друго, ако се усвоји пракса копирања кода, пре или касније се дешава да исправка или раније планирана измена која је учињена на једном месту, не буде пренета у све копије у које би требало. Због таквог стања кода, може да буде потрошено доста времена да се установи да ли неке разлике које су у међувремену настале треба да се елиминишу или не, односно која верзија треба да се прилагоди оној другој. Ситуација је још компликованија ако уместо две постоји више верзија сличног кода. Све у свему, дугорочно гледано, копирање је такође лоша идеја.

На срећу, постоји и трећа могућност, а то је управо употреба наслеђивања. Наслеђивање нам омогућава да искористимо и прилагодимо то што већ постоји, а да при томе нити реметимо рад других програмера мењајући постојећу класу, нити умножавамо постојећи кôд, компликујући одржавање.

Базна и изведена класа

Механизам наслеђивања нам омогућава да направимо нову класу, користећи неку постојећу класу као полазну основу. Ту полазну класу називамо базна класа, а нову класу изведена класа. Када помоћу механизма наслеђивања формирамо нову класу на основу постојеће, нова класа одмах (без копирања кода) садржи сву функционалност базне класе, коју затим може да допуни, а по потреби и делимично да измени (о измени ће бити речи у лекцији о виртуелним методима).

Да би поља базне класе била доступна у методима изведене класе, довољно је да та поља у базној класи буду декларисана као заштићена (protected) уместо као приватна (private). Тако декларисана поља ће у свим осталим класама (које нису изведене из базне) и даље бити недоступна, исто као када их декларишемо као приватна.

Доступност

public

protected

private

Унутар исте класе

Унутар изведених класа

✅️

Из осталих класа

✅️

Писање и понашање изведене класе

Навођење базне класе као полазне основе за нову класу

Јасно је да приликом писања дефиниције изведене класе треба негде да се наведе из које класе се изводи та класа, да би компајлер имао све потребне информације за генерисање извршивог кода. Име базне класе се наводи након имена изведене класе и двотачке. На пример, ако изводимо класу B из класе A, треба писати:

public class A
{
    // ...
}

public class B : A
{
    // ...
}

Процес креирања објекта изведене класе, писање конструктора

Приликом креирања инстанце изведене класе, потребно је да се иницијализује и онај део објекта који постоји и у базној класи. Пошто се то већ дешава у конструктору базне класе (а може да буде сложен поступак), било би непрактично да се кôд за иницијализацију основног дела понавља у конструктору изведене класе. Стога је механизам наслеђивања осмишљен тако да се при инстанцирању изведене класе увек прво позива конструктор базне класе, а затим конструктор изведене класе. На тај начин нам је омогућено да се конструктором изведене класе само надовежемо на оно што ради конструктор базне класе и да његов рад само допунимо (и евентуално модификујемо колико је потребно), али не морамо да га понављамо.

Приликом писања конструктора изведене класе, имамо могућност да наведемо који конструктор базне класе желимо да се изврши пре њега (а неки мора да се изврши), и са којим аргументима. Аргументе конструктора базне класе наводимо након листе аргумената конструктора изведене класе, двотачке и кључне речи base, на пример:

public class A
{
    protected int x;
    public A(int x0) { x = x0; }
    // ...
}

public class B : A
{
    private int y;
    public B(int x0, int y0)
        : base(x0)
    {
        y = y0;
    }
    // ...
}

Ако базна класа има више конструктора, компајлер на основу броја и типа аргумената наведених иза речи base одређује који од њих треба да буде извршен. Уколико базна класа има и конструктор без параметара, део : base(<lista argumenata>) можемо и да изоставимо, а у том случају ће да буде извршен конструктор базне класе без параметара. Уколико базна класа нема такав конструктор, реч base и листу аргумената морамо да наведемо.

Примери наслеђивања

Производи и намирнице

У овом примеру, производе који се купују у самопослугама представљамо помоћу само два податка, а то су назив и цена производа. У реалистичнијем примеру могло би се за сваки производ памтити више података, нпр. пореска стопа (ПДВ), количина (број комада у паковању), величина (маса или запремина) итд. Међутим, назив и цена су нам довољни да илуструју идеју. Са овако једноставно дефинисаном класом може, на пример, да се израчуна укупна цена групе неких производа.

Програм исписује:

Ukupna cena je 1370 dinara.

Млеко и сир се од осталих наведених производа разликују по томе што су кварљиви, односно имају рок трајања. Рецимо да нам је потребно да, за оне производе који имају рок трајања, проверимо да ли је некима од њих истекао рок, колико их је, која им је вредност и слично. Као што смо објаснили на почетку лекције, није добро решење да само преправимо класу Proizvod тако што јој додамо поље rokTrajanja, а не желимо ни да умножавамо копије постојећег кода. Зато можемо да дефинишемо класу Namirnica, којом ћемо да наследимо класу Proizvod. У нову класу додајемо поље rokTrajanja и одговарајуће својство за читање вредности. Тако долазимо до следећег програма.

Програм исписује

Ukupna cena je 1370 dinara.

Broj namirnica kojima je istekao rok je 1.
Ukupna vrednost tih namirnica je 120 dinara.

Класа Proizvod у овом примеру се односи на било који производ, био он намирница или не. Управо смо видели да намирнице (објекти изведене класе Namirnica) могу да се држе у истој колекцији са осталим производима и да могу да учествују у обради (сабирању цена) на исти начин као и остали производи. Међутим, поред тога, за намирнице је могућа и посебна врста обраде (провера рока трајања), која није могућа за производе који нису намирнице. Према томе, намирница јесте производ, али посебна врста производа. У овом примеру, поред особина које има сваки производ (назив и цена), намирница има и додатну особину (рок трајања).

Возила и аутобуси

За сва возила која користе течно гориво (аутомобили, камиони, моторцикли, аутобуси, трактори) може да се води евиденција о наточеном гориву, пређеној километражи, преосталом гориву у резервоару, домету са расположивим горивом и слично. У ту сврху служи класа Vozilo из овог примера. Поред ове евиденције о гориву и километражи, за аутобусе може додатно да буде од интереса евиденција о превезеним путницима, попуњености аутобуса, продатим картама за вожњу и слично. Зато је класа Autobus изведена из класе Vozilo и допуњена подацима и методима за праћење броја путника. Требало би да су класе и пример употребе довољно јасни и без детаљнијег објашњења.

Програм исписује

Vozilo v moze da predje jos 500Km.
Vozilo a moze da predje jos 800Km.
U vozilu a ima jos 40 mesta za putnike.

Слично као у примеру са производима и намирницама, и овде смо видели две класе. Класа Vozilo је општија и зато базна, а класа Autobus је посебна врста возила и као таква изведена из класе Vozilo. Објекат класе Autobus може да се користи у обради као да је објекат класе Vozilo, што се демонстрира позивима метода a.Natoci, a.Predji и читањем својства a.Domet. Поред овога, за аутобусе је могућа и додатна обрада информација, специфична за класу Autobus, а то је евиденција о броју путника (методи Ulaz, Izlaz, својства BrPutnika, BrSlobodnihMesta).

Наслеђивање као однос између класа

До сада смо имали прилике да видимо различите облике сарадње између класа. Чест случај је био да једна класа креира, а затим и користи објекте друге класе. На пример, у свакој конзолној апликацији класа Program креира и користи објекте осталих класа. Ову врсту односа зовемо асоцијација.

Било је и ситуација у којима објекат једне класе у својој дефиницији садржи референце на објекте других класа. На пример, класа Prava садржи референце на једну тачку и један вектор. Такође, у графичким апликацијама класа која представља форму типично садржи референце на све остале класе које се појављују у апликацији. Ову врсту односа зовемо агрегација.

Наслеђивање представља нову врсту односа између класа, који називамо специјализација. То значи да објекти изведене класе могу да се посматрају као посебна врста објеката базне класе, у смислу да са њима може да се ради све што и са објектима базне класе. Обрнуто не важи, јер са објектима изведене класе може да се ради и нешто додатно, специфично за изведену класу, што са (правим) објектима базне класе није могуће.

У свим примерима наслеђивања, објекат изведене класе треба да буде у стању да одигра улогу објекта базне класе, тј. да уме све што уме и објекат базне класе (а може да уме и више). Другим речима, кôд који користи објекте базне класе треба да буде у стању да уместо њих употреби објекте изведене класе, а да то не мора ни да зна.

Приликом одлучивања да ли једна класа треба да буде изведена из друге, често је корисно да себи поставимо питање да ли однос између тих класа можемо да опишемо речима: „…је врста од…”. Као што смо већ видели, класе из наших примера пролазе овај тест: намирница јесте посебна врста производа, а аутобус јесте посебна врста возила.

(Created using Swinx, RunestoneComponents and PetljaDoc)
© 2022 Petlja
A- A+