PHP a abstraktné triedy

Published:

Add / read comments

V minulom článku som sa zaoberal dedením v triedach, potom v ďalšom riadením prístupu do tried a na tieto články chcem nadviazať problematikou abstraktných tried.

Mali sme teda materskú triedu "Chemikalie", v ktorej bola verejná metóda:

// verejna metoda v materskej triede
public function vytvorPlneInfo() {	  
  // vygeneruje kompletne info zlucenine (nazov+vzorec)
  return $this->systematicky_nazov." (".$this->trivialny_nazov.") [".$this->vzorec."]";	
}

Túto metódu rozširovala verejná metóda v detskej triede:

//rozsirime matersku metodu
public function vytvorPlneInfo() {
  $prem = parent::vytvorPlneInfo();
  $prem .= "Skupenský stav = $this->skupenstvo";
  return $prem;
}

Nežiadúcim javom je to, že sa dá dostať k hodnotám z materskej metódy nasledovne:

//vytvorim instanciu (objekt) materskej triedy  
$matka = new Chemikalie ('etín','acetylén','C2H2','firma s.r.o.');
echo 'Verejná metóda materského objektu: '. $matka->vytvorPlneInfo();

A to je problém, chcem totiž, aby sa dalo dostať iba k hodnotám, ktoré vytvoria detské metódy. To by sa dalo zabezpečiť nastavením materskej metódy na typ "protected". Tým by sa už nedalo k metóde dostať ani po inicializácii materského objektu.

//Fatal error: Call to protected method Chemikalie::vytvorPlneInfo() from context
echo 'Verejná metóda materského objektu: '. $matka->vytvorPlneInfo();

Ďalším spôsom ako ochrániť materskú triedu je zadefinovať ju ako "abstraktnú". Tým nebude možné vytvoriť (inicializovať) ani jej objekt (inštanciu).

abstract class Chemikalie {
  ...
}
//Fatal error: Cannot instantiate abstract class Chemikalie	
$matka = new Chemikalie ('etín','acetylén','C2H2','firma s.r.o.');

Odteraz bude možné sa k metódam zadefinovaným v materskej triede dostať iba prostredníctvom dcérskych tried.

//vytvorim instanciu (objekt) detskej triedy 
$product = new TechnickeInfo('etín','acetylén','C2H2','firma s.r.o.','plyn');
//Verejná metóda 1. detského objektu, rozširuje materský
echo $product->vytvorPlneInfo();
//vystup:  etín (acetylén) [C2H2] Skupenský stav = plyn

A to samozrejme aj vtedy, ak bude metóda "vytvorPlneInfo" nastavená na typ "protected", keďže tento typ sa dedí. Ako typ "abstract" je možné nastaviť aj metódu triedy.

abstrakt protected function vytvorPlneInfo();

Avšak táto metóda nemôže mať žiadny obsah. Preto ak bude metóda mať nejaký obsah, interpeter PHP vypíše chybu:

//Fatal error: Abstract function Chemikalie::vytvorPlneInfo() cannot contain body
abstract protected function vytvorPlneInfo() {	  
  // ...	
}

Abstraktné metódy sa teda zadefinujú prázdne a potom sa využívajú v dcérskych triedach. V prípade ak ponechám dcérsku metódu v pôvodnom stave:

//rozsirime abstraktnu matersku metodu
public function vytvorPlneInfo() {
  $prem = parent::vytvorPlneInfo();
  $prem .= "Skupenský stav = $this->skupenstvo";
  return $prem;
}

po jej zavlaní PHP vyhodí chybu:

//Fatal error: Cannot call abstract method Chemikalie::vytvorPlneInfo()
echo $product->vytvorPlneInfo();

Dcérsku metódu je potrebné prepísať do nasledujúceho tvaru:

//implementuje abstraktnu matersku metodu
public function vytvorPlneInfo() {
  // vygeneruje kompletne info zlucenine (nazov+vzorec)
  $prem = $this->systematicky_nazov." (".$this->trivialny_nazov.") [".$this->vzorec."]";	
  $prem .= " Skupenský stav = $this->skupenstvo";
  return $prem;
}

po jej zavolaní dostaneme výstup:

//vystup: etín (acetylén) [C2H2]Skupenský stav = plyn
echo $product->vytvorPlneInfo();

Dôležité je spomenúť, že ak abstraktnú metódu zadefinujem, potom je potrebné ju v dcérskej triede aj implementovať. Inak interpreter PHP vypíše chybu:

//Fatal error: Class TechnickeInfo contains 1 abstract method and must therefore be declared abstract or implement the remaining methods

Ďalej je potrebné spomenúť, že viditeľnosť vlastností a metód (funkcií) v detskej triede musí byť rovnaká, alebo menšia ako v materskej triede. Preto ak by som ponechal vlastnosti v materskej triede na type "private":

// privatne parametre (vlastnosti) triedy
private $systematicky_nazov;
private $trivialny_nazov;
private $vzorec;

tak by sa detská metóda (funkcia), ktorá implementuje abstraktnú materskú nemohla dostať k vlastnostiam objektu. Preto je potrebné ich nastaviť na typ "protected". A vtedy sú k dispozícii.

// privatne parametre (vlastnosti) triedy
protected $systematicky_nazov;
protected $trivialny_nazov;
protected $vzorec;

Na toto treba dávať pozor. Ja som sa tiež najprv čudoval, prečo mám premenné prázdne. PHP pritom vôbec neprotestuje a je ťažké tieto chyby hľadať.

Ak som však dobre pochopil podstatu abstraktných tried, majú za úlohu byť základňou funkcií, ktoré majú byť implementované v detských triedach, ktoré ich rozširujú. Preto pôvodný koštruktor v materskej abstrakt triede je nežiadúci.

// konstruktor objektu
public function __construct( $systematicky_nazov, $trivialny_nazov, $vzorec, $vyrobca ) {
 
  // inicializaciou triedy, vytvorim objekt triedy (jej instanciu), ktory ma taketo vlastnosti
  $this->systematicky_nazov = $systematicky_nazov;
  $this->trivialny_nazov = $trivialny_nazov;
  $this->vzorec = $vzorec;
  $this->vyrobca = $vyrobca;				
 
}

Je potrebné ho v tomto prípade nahradiť nejakou funkciou, ktorá spracuje argumenty konštruktoru detských objektov a to nasledovnou:

protected function setChemikalie( $systematicky_nazov, $trivialny_nazov, $vzorec,	$vyrobca ) {
 
  $this->systematicky_nazov = $systematicky_nazov;
  $this->trivialny_nazov = $trivialny_nazov;
  $this->vzorec = $vzorec;
  $this->vyrobca = $vyrobca;				
 
}

Aby boli vlastnosti objektu prístupne po inicializácii inštancie, je potrebné zmeniť konštruktor detského objektu nasledovne:

//detske konstruktory rozsiruju materske o vlastne parametre (vlastnosti)
public function __construct( $systematicky_nazov, $trivialny_nazov, $vzorec, $vyrobca, $skupenstvo ) {
 
  parent::setChemikalie( $systematicky_nazov, $trivialny_nazov,	$vzorec, $vyrobca );
  $this->skupenstvo = $skupenstvo;
 
}

Potom sa v prostredí klientského kódu môže vytvoriť objekt detskej triedy a využiť metóda "vytvorPlneInfo" nasledovne:

//vytvorim instanciu (objekt) detskej triedy 
$product = new TechnickeInfo('etín','acetylén','C2H2','firma s.r.o.','plyn');
//Verejná metóda 1. detského objektu, rozširuje abstraktny materský
echo $product->vytvorPlneInfo();
//vystup:
//etín (acetylén) [C2H2] Skupenský stav = plyn

Podobne môžem písať aj pre druhú detskú triedu:

//vyuzijem abstraktnu matersku metodu
public function vytvorPlneInfo() {
  // vygeneruje kompletne info zlucenine (nazov+vzorec)
  $prem = $this->systematicky_nazov." (".$this->trivialny_nazov.") [".$this->vzorec."]";
  $prem .= " Cena = $this->cena [$this->mena/$this->jednotka]";
  return $prem;
}
 
//vystup: 
//etín (acetylén) [C2H2]Cena = 10 [euro/l]

Čiže na záver: ak chcem prinútiť, aby detská trieda implementovala nejakú metódu, zadefinujem jej materskú abstraktnú triedu. Táto trieda zvyčajne nemá konštruktor a nemôžem inicializovať jej inštanciu. Vzhľadom na to, že v oblasti abstraktných tried niesom ešte celkom doma a prípadne som si niektoré veci vysvetlil zle, kľudne ma opravte v diskusii. Rád sa priučím.

Published:

Add / read comments

FIND ME

Share, follow or connect with me.