PHPPamokos.lt


5. Paveldėjimas (inheritance)

Šioje pamokoje papasakosiu apie vieną iš esminių OOP privalumų, kuris naudojamas praktiškai kiekviename projekte - kalbu apie paveldėjimą ("inheritance").

Esmė tokia, kad kurdami klases, galite pastebėti tarp jų panašumų - sakykime, jei turite klasę Darbuotojas ir klasę Vadovas, kai kurie klasės metodai ar savybės gali sutapti: ir tas ir tas turi vardą/pavardę, ir tas ir tas gauna atlyginimą. Tik galbūt skiriasi priėmimo procesas, funkcionalumas ir kiti dalykai.

Kitas pavyzdys - transporto priemonės. Sakykime, turime klases Automobiliai ir Motociklai. Vėlgi - jie turi ir panašumų, ir skirtumų: abu turi pagaminimo metus, kainą, kilmės šalį, markę/modelį ir t.t. Bet, sakykime, funkcija "važiuoti" greičiausiai skirsis.

Tai tokiais atvejais, kai galima išskirti dalį metodų/savybių į kažkokią apčiuopiamą visumą, apsimoka kurti tėvinę klasę ("parent"), kurioje būtų aprašyti bendri veiksmai, o "vaikų" klasėse jau būtų tie individualūs dalykai. Mūsų pavyzdžiuose tėvinės klasės galėtų vadintis Žmogus ir TransportoPriemone.

Čia galima prisiminti ir pirmose pamokose aptartą paveiksliuką su namais. Priminsiu:

Čia mintis kad iš vienos klasės House galime kurti du skirtingus objektus ir su jais dirbti atskirai. Bet įsivaizduokime kad virš tos klasės yra dar viena klasė pavadinimu RealEstate, kurioje aprašoma viskas, kas yra bendra nekilnojamam turtui. O tada jau atskira klasė namui, butui ar sklypui. Tada sakoma, kad tos išvardintos klasės yra paveldėtos nuo "tėvinės" klasės.

Kaip tai atrodo kode:

class RealEstate {
	var $price;
	var $owner;

	function buy($owner, $price) {
		$this->owner = $new_owner;
		$this->price = $price;
	}

	function printDocument() {
		// Print PDF document with $this->price;
	}
}

class House extends RealEstate {
	var $type;
	var $stores;

	function build($type, $stores) {
		$this->type = $type;
		$this->stores = $stores;
	}
}

$house = new House;
$house->build('brick', 2);
$house->buy('Povilas', 100000);
$house->printDocument();

Pradžioje paaiškinsiu prasmę - "tėvinėje" klasėje RealEstate galima nurodyti objekto kainą ir šeimininką, o taip pat atlikti pirkimo veiksmą bei atspausdinti objekto dokumentą. O klasė House, kuri paveldi savo savybes ir metodus iš "tėvinės" klasės (tą nurodo žodis extends po pavadinimo), turi savo "pribumbasų" - namui galima nurodyti, kokio jis tipo (mūrinis, blokinis, plytinis ir kt.) bei kiek aukštų turi. Taip pat galima ir pastatyti namą - tą atlieka metodas build().

Turbūt pastebėjote, kad "dukterinė" ("child") klasė prieina prie visų savybių ir metodų iš "tėvinės" klasės. Nors kuriame objektą iš klasės House, galima iškviesti metodus buy ir printDocument, nors jie yra aprašyti tik klasėje RealEstate ir nėra aprašyti klasėje House. Dėl to tai ir yra paveldėjimas - iš savo tėvų paveldime dvi akis ir galime jomis žiūrėti, bet galime sau nusipirkti akinius ir žiūrėti geriau/kitaip.

Dar vienas paveldėjimo privalumas - kad jis nėra ribojamas vienu lygiu. Kitaip tariant, gali būti klasė Bungalow, paveldima nuo klasės House, o toliau klasė MyHouse paveldima nuo klasės Bungalow. Tokiu būdu galima kurti lanksčią ir, iš esmės, neribotą architektūrinę struktūrą, svarbu nesusipainioti.

Taip pat reikia paminėti terminus: kartais "tėvinė" klasė dar vadinama "bazinė" klasė ("base class") arba "superklasė" ("superclass"). O "dukterinė" klasė kartais vadinama "subklasė" ("subclass") arba anglišku terminu "derived class".

Metodų perrašymas (override)

Būna ir tokių situacijų, kai tas pats metodas tėvinėje ir dukterinėje klasėje skiriasi: sakykime, tas pats medinio namo statybos procesas gan stipriai skiriasi nuo daugiabučio namo statybų. Tokiu atveju yra galimybė perrašyti metodus (angl. "override"), ir kviesti būtent tokį kokį reikia.

class RealEstate {
	var $year;

	function buy($year) {
		$this->year = $year;
	}
}

class House extends RealEstate {
	function buy($year) {
		if ($year < 1987) $this->checkHouse();
		$this->year = $year;
	}
}

Šiame pavyzdyje namui yra patikrinama, ar jis ne senesnis kaip 1987 metų - sakykime, kad senesniems namams reikalingas papildomas patikrinimas. Bet patikrinimas reikalingas būtent namams, o ne bet kuriam nekilnojamam turtui - dėl to šis metodas yra perrašomas būtent dukterinėje klasėje.

Tiesa, ar pastebėjote, kad dukterinėje klasėje veikia kintamasis $this? Taip, galima kreiptis į savybes, nors jos neaprašytos dukterinėje klasėje.

Būna ir tokių situacijų, kad metodas tėvinėje ir dukterinėje klasėje skiriasi labai nedaug - sakykime, kad reikia atlikti tą patį, ką daro tėvinė klasė, o po to ant viršaus dar pridėti kokį nors veiksmą. Tad iš dukterinės klasės galima iškviesti tėvinės klasės metodą, su prefiksu parent::

class RealEstate {
	var $year;

	function buy($year) {
		$this->year = $year;
	}
}

class House extends RealEstate {
	function buy($year) {
		parent::buy($year);		
		if ($year < 1987) $this->upgradeHouse();
	}
}

Šiame pavyzdyje namas vis tiek perkamas, bet jei jis senesnis nei 1987 metų - daroma kažkokia renovacija.

Dar gali pasitaikyti toks poreikis (retai, bet kas čia žino), kai norėsite uždrausti metodo perrašymą - sakykime, kuriate savo klasę ir nenorite kad kitas programuotojas, kurdamas dukterinę klasę, perrašytų kažkokį metodą. Tam tikslui galima prieš metodo aprašymą prirašyti žodį final, tada dukterinės klasės metodas su tokiu pavadinimu iššauks PHP klaidą:

class RealEstate {
	var $year;

	final function buy($year) {
		$this->year = $year;
	}
}

Kada ir kaip taikyti paveldėjimą

Susipažinote su tuo, kaip sukurti tėvines ir dukterines klases, bet turbūt svarbesnis klausimas - kada būtent taikyti tokį principą, kada apsimoka naudoti paveldėjimą, o kada ne. Būtent todėl pačioje kurso pradžioje akcentavau, kad reikia pakeisti mąstyseną ir požiūrį - nors PHP kodą galima rašyti pakankamai greitai, bet OOP verčia labiau mąstyti prieš parašant pirmą kodo eilutę.

Realiai reikėtų prisėsti ir sumąstyti visą būsimo projekto architektūrą, dar geriau schematiškai ją nubraižyti, tam gali pasitarnauti UML kalba arba tiesiog bet kokia schemų braižymo programa. Tinka ir popierius+pieštukas, svarbu galutinis rezultatas.

OOP diagramos pavyzdys:

Turėdami visą klasių struktūrą prieš akis, ne tik pats geriau suprasite būsimą projektą, bet ir išvengsite kardinalių perdarymų ateityje. Dar daugiau - pagal tokią schemą galima projektuoti ir duomenų bazės struktūrą, pririšant vieną klasę prie konkrečios DB lentelės.



(c) 2015-2018. Visais klausimais kreipkitės povilas@laraveldaily.com