Liigu peamise sisu juurde

Sissejuhatus pärilikkusesse

Õppevideo antud teemal:

Sissejuhatus

Objektorienteeritud programmeerimise üks alustalasid on see, et klasse saab luua teiste klasside alusel. Selle asemel, et kirjutada sama koodi mitmel korral, saab uus klass pärida olemasoleva klassi välju ja meetodeid ning neid laiendada uue käitumisega.

Seda suhet programmeerimises nimetatakse pärilikkuseks (inheritance). Olemasolevat klassi nimetatakse ülemklassiks (superclass) ja klassi, mis sellest pärib, alamklassiks (subclass).

Miks pärilikkus eksisteerib?

Oletame, et meil on süsteem, mis kirjeldab erinevaid sõidukeid. Neid sõidukeid on erinevat tüüpi: auto, veoauto ja mootorratas. Kõik jagavad ühiseid omadusi, milleks on mark ja kiirus, ning ühist käitumist. Ilma pärilikkuseta peaks neid välju ja meetodeid igas klassis eraldi uuesti välja kirjutama:

class Car {
private String brand;
private int speed;

public Car(String brand, int speed) {
this.brand = brand;
this.speed = speed;
}

public String getBrand() { return brand; }
public int getSpeed() { return speed; }

public void drive() {
System.out.println(brand + " is driving");
}
}

class Truck {
private String brand;
private int speed;

public Truck(String brand, int speed) {
this.brand = brand;
this.speed = speed;
}

public String getBrand() { return brand; }
public int getSpeed() { return speed; }

public void drive() {
System.out.println(brand + " is driving");
}
}

Selle tulemusena tekib palju duplikaatkoodi, mis muudab süsteemi edasiarenduse ja hoolduse raskemaks. Näiteks drive() meetodi äriloogika muutumisel peaks seda muutma ka kõikides alamklassides.

extends võtmesõna

Selleks, et Javas pärilikkust saavutada tuleb klassi laiendada teisest klassist. Javas kasutatakse selleks extends võtmesõna. Alamklass, mis laiendab ülemklassi, pärib kõik selle public ja protected väljad ja meetodid. Loome oma süsteemile juurde Vehicle klassi ning refaktoreerime Car ja Truck klassi ümber:

class Vehicle {
private String brand;
private int speed;

public Vehicle(String brand, int speed) {
this.brand = brand;
this.speed = speed;
}

public String getBrand() { return brand; }
public int getSpeed() { return speed; }

public void drive() {
System.out.println(brand + " is driving");
}
}

class Car extends Vehicle {

public Car(String brand, int speed) {
super(brand, speed); // calls the Vehicle constructor
}
}

class Truck extends Vehicle {
private double payloadTons;

public Truck(String brand, int speed, double payloadTons) {
super(brand, speed);
this.payloadTons = payloadTons;
}

public double getPayloadTons() { return payloadTons; }
}

Vehicle klass koondab endasse Car ja Truck klassi ühisosad. Car klass ei korda koodi, kuna see pärib kõik omadused Vehicle klassilt. Truck klassile omakorda lisati payloadTons väli, mis näitab, kui mahukat lasti on võimalik selle sõidukiga vedada.

Kasutada neid saaks järgnevalt:

Car car = new Car("Toyota", 120);
car.drive(); // Toyota is driving
System.out.println(car.getBrand()); // Toyota

Truck truck = new Truck("Volvo", 90, 20.0);
truck.drive(); // Volvo is driving
System.out.println(truck.getPayloadTons()); // 20.0

"IS-A" seos

Pärilikkus kujutab endast "IS-A" seost ehk "A on B" seost. Enne pärilikkuse rakendamist tasuks üle mõelda, kas loodav alamklass on ikka ülemklassi tüüpi, näiteks:

  • Car Is-A Vehicle (õige)
  • Truck Is-A Vehicle (õige)
  • ParkingLot Is-A Vehicle - vale, parkla ise ei ole sõiduvahend, seal hoitakse sõiduvahendeid.

Kui antud juhul "Is-A" seos paika ei pea, siis ei tohiks probleemi lahendamiseks pärilikkust kasutada. Selle asemel kasutatakse kompositsiooni, mis on "Has-A" seos. "Has-A" seose puhul klass hoiab viidet, mitte ei laienda mingit ülemklassi.

Päritavad omadused

Alamklass pärib ülemklassilt järgnevaid omadusi:

  • Kõik public ja protected väljad
  • Kõik public ja protected meetodid

private väljad ja meetodid on küll loodavatel alamklassi objektidel olemas, kuid ligipääs neile puudub. Konstruktoreid ka ei pärita, kuid neid on võimalik kutsuda välja koos super() meetodiga.

class Vehicle {
private String brand; // not directly accessible in subclass

public String getBrand() { return brand; } // inherited
}

class Car extends Vehicle {
public void printBrand() {
// System.out.println(brand); // Error: brand has private access
System.out.println(getBrand()); // OK — using the inherited getter
}
}

Object klass

Kõik klassid Javas pärinevad Object klassist. Kuigi eraldi extends Object ei pea välja kirjutama, siis kompilaator käsitleb kõiki klasse sedasi, nagu seal oleks see kirjas.

class Vehicle { ... }
// is the same as
class Vehicle extends Object { ... }

See tähendab, et igal objektil Javas on olemas kindlad meetodid, nagu toString(), equals() ja hashCode(). Nende meetodite kohta saate lähemalt lugeda meetodite ülekirjutamise peatükis.

Seda suhet ilmestab järgmine UML klassidiagramm:

Üksikpärilikkus - Single inheritance

Javas klassid saavad ainult laiendada üht ülemklassi. Seda nimetatakse üksikpärilikkuseks (single inheritance). Kui proovida extends märksõna kaudu pärida mitut klassi, tekib kompileerimisviga:

class Car extends Vehicle { ... }           // OK

class SportsCar extends Car, Vehicle { ... } // Error: does not compile

Samas on võimalik saavutada sarnane tulemus mitmetasemelise pärilikkusega:

class Vehicle { ... }
class Car extends Vehicle { ... }
class SportsCar extends Car { ... } // SportsCar inherits from both Car and Vehicle

SportsCar pärib omadusi Car klassist ning viimane omakorda Vehicle klassist. See tähendab, et pärilikkus Javas on transitiivne ehk antud näite puhul saame öelda, et SportsCar on nii Car kui ka Vehicle, kuid Car puhul saame öelda, et see on Vehicle, aga mitte SportsCar.

nõuanne

Üldiselt tuleks pärilikkust kasutada nii minimaalselt kui võimalik. Pärilusahelad peaksid olema lühikesed ja tähendusrikkad. Kolme- või neljatasemeline ahel on tavaliselt märk sellest, et disaini saab lihtsustada. Mida sügavamale minna, seda raskemini hallatavamaks kood muutub, kuna alamklassist aru saamiseks peab ka kõikidest ülemklassidest aru saama.


Pärilikkus võimaldab ühist käitumist kirjutada ühe korra ülemklassi ja taaskasutada seda paljudes alamklassides. Järgmised peatükid käsitlevad, mida alamklassid saavad teha sellega, mida nad pärivad, kuidas kutsuda ülemklassi konstruktoreid ja meetodeid ning kuidas pärilikkus võimaldab polümorfismi.