Sunteți pe pagina 1din 8

Nasljeđivanje, polimorfizam i abstraktne klase

Klasa predstavlja skup objekata sa zajedničkom građom i ponašanjem. Klasa


određuje strukturu objekta navođenjem varijabli koje su sadržane u svim
instancama klase i određuje ponašanje objekata preko metode instance koje
izražavaju ponašanje objekata. Ovo je moćna ideja, ali nešto poput ovog se može
postići u većini programskih jezika. Glavna novost objektno orijentiranog
programiranja u odnosu na tradicionalno je da klase mogu izražavati sličnosti
između objekata koji imaju zajedničke neke, ali ne sve dijelove strukture i
ponašanja. Ovakve sličnosti mogu biti izražene pomoću nasljeđivanja (inheritance)
i polimorfizma.

Naziv nasljeđivanje odnosi se na činjenicu da jedna klasa može


naslijediti dio ili svu strukturu i ponašanje od druge klase. Klasa
koja nasljeđuje zove se podklasa (subclass) klase od koje
nasljeđuje. Ako je klasa B podklasa klase A onda ja klasa
A nadklasa(superclass) klase B. Podklasa može nadopunjavati
strukturu i ponašanje klase koju nasljeđuje, a može i zamijeniti ili
izmijeniti naslijeđeno ponašanje, ali ne i naslijeđenu strukturu.
Odnos između podklase i nadklase je često prikazan kao dijagram
u kojem je podklasa prikazana ispod i povezana na nadklasu.

U Javi se može prilikom stvaranja nove klase daklarirati da je nova klasa podklasa
postojeće klase. U slučaju klase B koja se definira kao podklasa klase A to se
zapisuje ovako:
class B extends A {
.
. // dodaci na, i izmijene
. // onog što je naslijeđeno od klase A
.
}

Više se klasa može deklarirati kao podklase


iste nadklase. Podklase (mogu se zvati i
"srodne klase") imaju zajedničke dijelove
strukture i ponašanja - one koje naslijeđuju od
zajedničke nadklase. Nadklasa izražava ove
zajedničke strukture i ponašanja. Na slici
lijevo, klase B, C i D su srodne klase.
Naslijeđivanje se može protegnuti preko
nekoliko generacija klasa, što je prikazano na slici gdje je klasa E podklasa klase D
koja je, opet, podklasa klase A. U ovom slučaju, klasa E smatra se podklasom klase
A iako joj nije izravna podklasa.
Razmotrimo primjer programa koji prati registracije motornih vozila. Program
koristi klasu Vehicle koja predstavlja sva motorna vozila, a uključuje varijable
instance poput registrationNumber, owner i metode instance poput transferOwnership().
Ove su varijable i metode zajedničke svim vozilima. Tri podklase
klase Vehicle: Car, Truck i Motorcycle sadržavaju varijable i metode vezane uz
određene vrste vozila. Klasa Car može dodati varijablu
instance numberOfDoors, Truck klasa može dodati numberOfAxels, a Motorcycle klasa
može imati logičku varijablu hasSidecar.

Deklaracija ovih klasa bi u Javi u grubim crtama izgledala ovako:


class Vehicle {
int registrationNumber;
Person owner; // (uz pretpostavku da je klasa Person već
definirana)
void transferOwnership(Person newOwner) {
. . .
}
. . .
}
class Car extends Vehicle {
int numberOfDoors;
. . .
}
class Truck extends Vehicle {
int numberOfAxels;
. . .
}
class Motorcycle extends Vehicle {
boolean hasSidecar;
. . .
}

Pretpostavimo da je myCar varijabla tipa Car deklarirana i inicijalizirana izrazom:


Car myCar = new Car();

Uz ovakvu deklaraciju, program bi mogao pozivati myCar.numberOfDoors jer


je numberOfDoors varijabla instance u klasi Car. Budući da klasa Car nasljeđuje
klasu Vehicle, Car ima strukturu i ponašanje Vehiclea, to znači da također postoje
i myCar.registrationNumber, myCar.owner i myCar.transferOwnership().

U stvarnom svijetu automobili, kamioni i motocikli su stvarno vozila, kao i u


programu. Znači, objekt tipa Car, Truck ili Motorcycle je automatski i objekt
tipa Vehicle. To nas vodi do važnog zaključka:

Varijabla koja u sebi može sadržavati poziv na objekt klase A,


može sadržavati i poziv na objekt koji pripada bilo kojoj od podklasa klase A.
Stvarni učinak ovog u našem primjeru je da objekt tipa Car može biti pridjeljen
varijabli tipa Vehicle, tj. možemo pisati:
Vehicle myVehicle = myCar;

ili čak:
Vehicle myVehicle = new Car();

Nakon bilo kojeg od ovih izraza varijabla myVehicle sadrži poziv na Vehicle objekt
koji je instanca podklase Car. Objekt "pamti" da je zapravo Car, a ne samo Vehicle.
Podatak o stvarnoj klasi objekta je spremljen kao dio tog objekta. Čak je moguće
ispitati pripada li objekt danoj klasi koristeći instanceof operator:
if (myVehicle instanceof Car) ...

ovaj test određuje da li je objekt na kojeg pokazuje myVehicle stvarno Car.

Sa druge strane, ako je myVehicle varijabla tipa Vehicle, izraz pridjeljivanja:


myCar = myVehicle;

nije dozvoljen jer myVehicle može pokazivati na druge tipove iz klase Vehiclea a ne
samo iz klase Car. Slično kao kad računalo ne dozvoljava
pridjeljivanje int vrijednosti short varijabli, jer svaki int nije nužno i short. Rješenje
ovog slučaja je opet pretvaranje tipova. Ako slučajno znamo da se myVehicle uistinu
odnosi na Car, možemo pretvoriti tip izrazom:
myCar = (Car)myVehicle;

a čak se može pozvati i


((Car)myVehicle).numberOfDoors.

Pokažimo to na primjeru ispisivanja važnih podataka o vozilu:


System.out.println("Podaci o vozilu:");
System.out.println("Registracijski broj: "
+
myVehicle.registrationNumber);
if (myVehicle instanceof Car) {
System.out.println("Tip vozila: Car");
Car c = (Car)myVehicle;
System.out.println("Broj vrata: " + c.numberOfDoors);
}
else if (myVehicle instanceof Truck) {
System.out.println("Tip vozila: Truck");
Truck t = (Truck)myVehicle;
System.out.println("Broj osiju: " + t.numberOfAxels);
}
else if (myVehicle instanceof Motorcycle) {
System.out.println("Tip vozila: Motorcycle");
Motorcycle m = (Motorcycle)myVehicle;
System.out.println("Ima: " + m.hasSidecar);
}

Uočite da pri izvršavanju programa računalo provjerava da li je pretvaranje tipova


ispravno, tako ako se myVehicle odnosi na objekt tipa Truck, pretvaranje
tipa (Car)myVehicle će proizvesti pogrešku.

Za još jedan primjer, razmotrimo program koji radi s oblicima prikazanim na slici:
pravokutnicima (Rectangle), elipsama (Oval) i zaobljenim pravokutnicima
(Rounded Rectangle) raznih boja.

Klase Rectangle, Oval, i RoundRect predstavljaju tri vrste oblika. Ove tri klase imaju
zajedničku nadklasu Shape koja predstavlja zajedničke osobine svih triju
elemenata. Klasa Shape može sadržavati varijable instance koje predstavljaju boju,
položaj i veličinu oblika. Osim toga može sadržavati metode instance za mijenjanje
boje, položaja i veličine oblika. Radnja poput promjene boje sastoji se od promjene
varijable instance i ponovnog iscrtavanja oblika u novoj boji:
class Shape {

Color color; // Boja oblika. (Sjetite se da je klasa Color


// definirana u paketu java.awt. Pretpostavimo
// da je ova klasa uvedena.)

void setColor(Color newColor) {


// Metoda za izmjenu boje obliku.
color = newColor; // promijeni vrijednost varijable instance
redraw(); // ponovno iscrtaj oblik, novom bojom
}

void redraw() {
// metoda za iscrtavanje oblika
? ? ? // koje naredbe bi stavili ovdje?
}

. . . // još varijabli instance i metoda instance

} // kraj klase Shape

Problem s metodom redraw() je u tome što se svaki oblik crta različito.


Metoda setColor() može biti pozvana za bilo koji oblik, kako računalo može znati
koji oblik treba nacrtati koji oblik treba nacrtati kad izvršava metodu redraw()?
Mogli bi reći da se redraw() metoda izvršava tako da se od oblika traži da ponovo
nacrta sam sebe jer svaki objekt zna kako to uraditi.

Zapravo, to znači da svaki oblik ima vlastitu redraw() metodu:


class Rectangle extends Shape {
void redraw() {
. . . // naredbe za iscrtavanje pravokutnika
}
. . . // moguće, još metoda i varijabli
}
class Oval extends Shape {
void redraw() {
. . . // naredbe za iscrtavanje elipse
}
. . . // moguće, još metoda i varijabli
}
class RoundRect extends Shape {
void redraw() {
. . . // naredbe za iscrtavanje pravokutnik zaobljenih kuteva
}
. . . // moguće, još metoda i varijabli
}

Ako je oneShape varijabla tipa Shape, mogla bi se odnositi na objekt bilo kojeg od
tipova Rectangle, Oval, ili RoundRect. Kako se vrijednost varijable oneShape mijenja
u vrijeme izvršavanja programa tako se može odnositi na objekte različitih tipova.
Prilikom svakog izvršavanja izraza
oneShape.redraw();

poziva se ona redraw() metoda koja odgovara tipu objekta na koji


se oneShape odnosi. Iz koda programa ne mora biti očito koji će oblik ova metoda
iscrtati, jer to ovisi o objektu na kojeg se metoda u tom trenutku odnosi. Zanimljiv
je i slučaj ako se izraz poput oneShape.redraw()nalazi u petlji, jer ako
se oneShape mijenja za vrijeme izvršavanja petlje, taj će izraz pozivati različite
metode i crtati različite oblike. Kažemo da je metoda redraw() polimorfna. Metoda
je polimorfna ako radnja koju metoda obavlja ovisi o objektu na kojeg se
primjenjuje. Polimorfnost je jedna od važnih osobina koje izdvajaju objektno
orijentirano programiranje.

Možda će biti jasnije drugačije objašnjenje: Kod objektnog programiranja,


pozivanje metode se može smatrati i slanjem poruke objektu. Objekt odgovara
izvršavanjem prikladne metode. Dakle, izraz "oneShape.redraw()" je poruka objektu
na kojega pokazuje oneShape. Buduću da objekt zna koje je vrste, onda zna i kako
reagirati na tu poruku. S ove točke gledanja, objekti su aktivne jedinice koje
primaju i šalju poruke, a polimorfnost je prirodni, zapravo i neophodni dio ovog
načina programiranja. Polimorfnost znači samo da različiti objekti mogu različito
reagirati na istu poruku.
Pri svakom pozivu redraw() metode, izvršava se metoda iz odgovarajuće klase,
ovisno o vrsti objekta. Postavlja se pitanje: Što radi redraw() metoda u klasi Shape i
kako je definirati?

Odgovor je: Treba je ostaviti praznu! Činjenica je da Shape predstavlja neodređeni


oblik i da nema načina na koji bi se takav oblik mogao nacrtati. Moguće je nacrtati
samo određene likove poput pravokutnika ili elipsi. Potrebno je da
metoda redraw() bude definirana u Shape klasi da bi je se moglo pozivati
u setColor() metodi klase Shape ili da bi se uopće moglo izvršiti izraz
"oneShape.redraw()", jer je oneShape varijabla tipa Shape.

Zapravo, redraw() metoda iz klase Shape neće nikad biti pozvana, a i nema razloga
kreirati objekte tipa Shape. Varijable tipa Shape mogu postojati, ali će objekti na
koje će se odnositi uvijek pripadati nekoj od podklasa klase Shape. Kažemo da
je Shape apstraktna klasa. Apstraktne klase su one koje ne služe stvaranju objekata,
nego samo kao osnova za stvaranje podklasa, a služe samo da bi se izrazile
zajednička svojstva svih podklasa.

Slično tome, može se reći da je redraw() metoda u klasi Shape apstraktna metoda,
jer nije zamišljena da je se ikad pozove. Zapravo, ona i nema što raditi, jer se svo
crtanje obavlja redraw() metodama podklasa Shape. Ona govori da svi Shape objekti
razumiju redraw() poruku i određuje zajedničko sučelje za sve redraw() metode u
potklasama Shape.

Klasa Shape i njena redraw() metoda su po svom značenju apstraktne, što se može
zapisati i dodavanjem modifikatora "abstract" u njihovu definiciju. Kod apstraktnih
metoda blok naredbi koje inače opisuju metodu zamijenjen točka-zarezom (;), a
njihova primjena mora biti zadana u svim podklasama apstraktne klase. Apstraktna
klasa Shape izgleda ovako:
abstract class Shape {

Color color; // color of shape.

void setColor(Color newColor) {


// Metoda za izmjenu boje obliku.
color = newColor; // promijeni vrijednost varijable instance
redraw(); // ponovno iscrtaj oblik, novom bojom
}

abstract void redraw();


// apstraktna metoda -- mora biti definirana u
odgovarajućoj podklasi

. . . // još varijabli i metoda instance

} // kraj klase Shape


Kod ovakve definicije klase, postaje zabranjeno stvaranje bilo kakvih objekata koji
pripadaju ovoj klasi, a ako bi pokušali računalo bi prijavilo grešku.

Svaka klasa u Javi ima svoju nadklasu, a ako ona nije definirana, automatski se
pridjeljuje nadklasa Object. Klasa Object je predefinirana klasa iz paketa java.lang i
jedina je klasa koja nema nadklasu.

Stoga su zapisi:
class myClass { . . .

i
class myClass extends Object { . . .

sasvim jednakog značenja.

Sve klase su, izravno ili neizravno, podklase klase Object. To znači da objekt bilo
koje klase može biti pridjeljen varijabli tipa Object. Klasa Object predstavlja
najopćenitija svojstva koja imaju svi objekti, bez obzira kojoj klasi
pripadaju. Object je najapstraktnija od svih klasa.

Klasa Object koristi se kod rada s vrlo općenitim objektima. Na primjer, java ima
standardnu klasu java.util.Vector koja predstavlja listu objekata tipa Object.
Klasa Vector je vrlo pogodna jer Vector može sadržavati neograničen broj objekata i
raste prema potrebi. Budući su objekti u listi tipa Object, lista zapravo može
sadržavati objekte bilo kojeg tipa.

Program koji prati objekte tipa Shape koji su nacrtani na ekranu može spremiti te
oblike u Vector. Recimo da se Vector zove listOfShapes. Oblik oneShape može biti
dodan na kraj liste pozivanjem metode instance
"listOfShapes.addElement(oneShape)", a uklonjen s liste pozivom
"listOfShapes.removeElement(oneShape)". Broj oblika u listi se može dobiti pozivom
"listOfShapes.size()", a i-ti objekt se poziva izrazom "listOfShapes.elementAt(i)".
Potrebno je voditi računa o tome da ovaj poziv vraća tip Object a ne Shape. Budući
je poznat tip objekata, moguće je izlazni tip objekta pretvoriti u Shape izrazom
poput:
oneShape = (Shape)listOfShapes.elementAt(i).

Razmotrimo primjer ponovnog iscrtavanja svih olika u listi pomoću for petlje, u
kojemu je lijepo prikazana polimorfnost i objektno orijentirano programiranje:
for (int i = 0; i < listOfShapes.size(); i++) {
Shape s; // i-ti element liste, smatra se da je Shape
s = (Shape)listOfShapes.elementAt(i);
s.redraw();
}

Korisno je razmotriti i aplet koji koristi apstraktnu klasu Shape i Vector u kojemu je
spremljena lista oblika:

Pritiskom na jednu od tipki u dnu apleta, odgovarajući oblik će biti dodan u


gornjem lijevom kutu apleta. Boja je određena izbornikom u donjem desnom kutu.
Oblik se može pomicati po ekranu, a pri tom će zadržavati odnos naprijed-nazad s
obzirom na druge oblike na ekranu. Oblik se može izvući ispred ostalih tako da se
drži pritisnita tipka shift prilikom izabiranja tog oblika.

Jedina upotreba klase oblika je prilikom iscrtavanja oblika na ekranu. Nakon


iscrtavanja, oblikom se upravlja kao s apstraktnim oblikom. Potprogram koji
omogućava pomicanje oblika radi s varijablama tipa Shape. Kako se oblik pomiče,
potprogram poziva metodu za iscrtavanje iz klase Shape, pa ne mora znati ni kako
se iscrtava taj oblik ni kojeg je oblik tipa. Objekt sam odgovara za svoje
iscrtavanje. Prilikom dodavanja novog oblika bilo bi dovoljno definirati novu
podklasu klase Shape i dodati tipku na aplet, nikakve druge promjene u programu
ne bi bile potrebne.

Pogledajte izvorni kod prikazanog apleta ShapeDraw.