class DogKeeper extends Person {
// methods & attributes for DogKeeper
// in addition to Person
}
Tomáš Pitner, Radek Ošlejšek, Marek Šabo, Jakub Čecháček pitner@muni.cz
Dědičnost. Nejlepší objektově-orientovaný způsob, jak zbohatnout.
Dědičnost je charakteristická vlastnost objektových jazyků se třídami, jako jsou Java, Python, C# a další.
U beztřídních (klasický JavaScript) se může řešit pomocí prototypů.
Objektové třídy jsou obvykle podtřídami, tj. speciálními případy, jiných tříd:
class DogKeeper extends Person {
// methods & attributes for DogKeeper
// in addition to Person
}
Všechny objekty typu DogKeeper
jsou současně typu Person
.
Místo jiné osoby lze použít DogKeeper
.
Person p = new DogKeeper("Karel");
V Javě dědí každá třída od třídy Object . |
superclass, "bezprostřední předek", "rodičovská třída"
subclass, "bezprostřední potomek", "dceřinná třída"
je specializací své nadtřídy
přebírá vlastnosti nadtřídy
zpravidla přidává další vlastnosti, čímž nadtřídu rozšiřuje (extends
)
Dědičnost by měla splňovat vztah je — každý DogKeeper
je Person
.
Každé oddělení má osobu vedoucího; je ale špatně dědit Department extends Manager
protože neplatí Department
je Manager
, ale Department
má Manager
.
Abychom zohlednili konceptuální vztah obecnější vs. speciálnější typ.
Abychom se vyhnuli opakování kódu a dosáhli znovupoužití (= kód metod a atributů se podědí, nemusíme jej znovu psát).
Mělo by platit oboje, aby mělo smysl dědičnost použít.
Dědění může být vícegenerační:
public class Manager extends Employee { ... }
public class Employee extends Person { ... }
Manažer podědí metody a atributy ze třídy Employee i (přeneseně) z Person.
Java vícenásobnou dědičnost ve smyslu dědění z více tříd současně nepodporuje!
Důvodem je problém typu diamant:
class DoggyManager extends Employee, DogKeeper { }
class Employee { public String toString() { "Employee"; } }
class DogKeeper { public String toString() { "DogKeeper"; } }
new DoggyManager().toString(); // we have a problem!
Vícenásobná dědičnost je možná jedině u rozhraní, kde metody nemají definovanou implementaci.
Dědičnost (v kontextu Javy) znamená:
potomek dědí všechny vlastnosti nadtřídy (= metody & atributy třídy)
poděděné vlastnosti potomka se mohou změnit (např. překrytím metody)
potomek může přidat další vlastnosti
vyhýbáme se duplikaci kódu
kód je kratší
když potřebuji upravit předka, musím upravit změny ve všech potomcích, což může být netriviální
méně závislostí, více případně i opakovaného kódu
více používané v praxi
Konstruktor musí volat vybraný konstruktor nadtřídy, nebo vlastní přetížený konstruktor (pomocí this();
s případnými parametry).
Konstruktor nadtřídy se volá pomocí super();
s případnými parametry.
Podobně jako u this()
, volání super();
musí být první příkaz a může být pouze jedno.
V konstruktoru nezle ani zkombinovat super()
s this()
.
Pokud super()
i this()
chybí, volá se automaticky bezparametrický konstruktor nadtřídy (tj. vloží se super()
). Ten ale musí existovat a být neprivátní.
Obecně platí, že volaný konstruktor musí v nadtřídě existovat.
Account
public class Account implements Informing {
private Person owner;
private int balance;
public Account((Person owner, int balance) {
this.owner = owner;
this.balance = balance;
}
public boolean debit(int amount) {
if(amount <= 0) return false;
balance -= amount;
return true;
}
}
CheckedAccount
Rozšíříme třídu Account
tak, že bude mít minimální zůstatek.
public class CheckedAccount extends Account {
private int minimalBalance;
public CheckedAccount(Person owner, int minBal, int initBal) {
super(owner, initBal); // calling Account constructor
if(initBal < minBal) { // is initial balance sufficient?
throw new IllegalArgumentException("low initial balance");
}
this.minimalBalance = minBal;
}
public CheckedAccount(Person owner) {
this(owner, 0, 0);
}
}
Vylepšíme třídu CheckedAccount
tak, aby zůstatek nebyl nižší než minimální zůstatek
Realizujeme tedy změnu metody debit
pomocí jejího překrytí (overriding)
public class CheckedAccount extends Account {
private int minimalBalance;
// construktors ...
@Override
public boolean debit(int amount) {
// check min. balance
if(getBalance() - amount >= minimalBalance) {
return super.debit(amount); // the debit is inherited
} else return false;
}
}
Konstrukce super.metoda(…);
značí, že je volána metoda předka, tedy třídy Account
.
Kdyby se super
nepoužilo, zavolala by se metoda debit
třídy CheckedAccount
a program by se zacyklil!
Takto lze volat jen bezprostředního předka. Něco jako super.super.debit(amount)
se syntaktická chyba.
Nepoužívejte chráněné (protected
) atributy. Je to "bad practice". Používejte zásadně privátní atributy s veřejnými nebo chráněnými gettery a settery.
Nikdy nepřekrývejte atributy! Tj. podtřída nesmí nikdy obsahovat atribut se jménem, jako má některá z jejích nadtříd. Přesto, že je to syntakticky možné.
public class CheckedAccount extends Account {
private int minimalBalance;
private int balance; // CHYBA - PREKRYTI ATRIBUTU Z NADRIDY
// construktors ...
@Override
public boolean debit(int amount) {
// check min. balance
if(getBalance() - amount >= minimalBalance) {
return super.debit(amount); // the debit is inherited
} else return false;
}
}
Často se používá skládání (kompozice) objektů, kdy objekt nedědí, ale nese odkaz na jiný objekt.
public class CheckedAccount {
private int minimalBalance;
private Account account;
public CheckedAccount(Person owner, int minBal, int initBal) {
account = new Account(owner, initBal);
...
}
// account.debit(amount)
}
Kompozicí se zabývá navazující kurz PV168 Seminář Javy.
Problémy s hierarchiemi dědičnosti pomocí kompozice řeší některé návrhové vzory, např.