Objekty v PHP5 - Skládání objektů, abstrakce a dědičnost

Wed, 16 Jul 2014 22:11:16 +1100

V dnešním článku si povíme něco víc o objektech, a to konkrétně o skládání objektů, dědičnosti, abstrakci a samozřejmě i viditelnosti, jejíž znalost je k výše uvedeným technikám nezbytná.

Na první pohled se může zdát o složité termíny, ale jejich samotné použití nijak náročné není. Mnohem složitější je správné použití. Nepřidávat dědičnost jen proto, abychom ukázali, že ji umíme, nenaplácat všechna klíčová slova do jedné třídy jenom aby tam byly. To je také hlavní důvod, proč píšu samostatný text, důvod, proč nemohu vše demonstrovat na své databázové třídě.

Skládání objektů

Objekty skládáme ve chvíli, kdy třída A uloží do členské proměnné instanci třídy B a do jejího konstruktoru předá sama sebe. Tímto způsobem to může jít pořád dál, takže v každé třídě aplikace budeme mít přístup k veřejným metodám všech ostatních tříd. Předání objektu neprobíhá přes kopii jako u proměnných, nýbrž přes referenci. Nevzniká X kopií databázové třídy, ale pouze jedna, a jakákoli změna nastavení se projeví ve všech referencích na daný objekt.

Tato definice ale zní strašně krkolomně, takže si ukažme příklad ze života.

Mějme třídu Environment, která vytváří základní prostředí aplikace. V členských proměnných má uložené instance některých tříd, které budou volány napříč celou aplikací. V našem příkladu to bude třída Db (databáze) a třída Debug (s nějakým uživatelským výstupem pro ladění). Třída Env pak vytvoří instanci samotného modulu, který už se bude starat o obsluhu konkrétní části webu. V ukázce je kontroler pro administraci. Ten vytvoří instanci další třídy, která už bude zpracovávat jednotlivé části administračního prostředí: kategorie, články, produkty, formuláře... Třída Db je prázdná, aby nám ukázka neházela fatal error, Debug obsahuje akorát metodu s funkcí print_r() pro ukázku rekurzivního předávání objektů.

Takže pokaždé, kdy postoupíme hlouběji do aplikace, daná třída předá sama sebe dál.

Je samozřejmě důležité, aby všechny parametry obsahující referenci na jiný objekt měly viditelnost public - pokud tedy nechceme přístup explicitně zakázat. Kostra by mohla vypadat například takto:

class Db { }

class Debug {

	public $env;

	public function __construct(Environment $env){
		$this->env = $env;
	}

	public function printDebug(){
		print_r($this);
	}
}

class Environment {

	public $db;
	public $debug;
	public $module;

	public function __construct(){
		$this->db = new Db();
		$this->debug = new Debug($this);
		$this->module = new MasterAdminController($this);
	}
}

class MasterAdminController {

	public $env;
	public $localModule;

	public function __construct(Environment $env){
		$this->env = $env;

		$this->localModule = new ArticleAdminController($this);
	}
}

class ArticleAdminController {

	public $parentModule;

	public function __construct($parent_controller){
		$this->parentModule = $parent_controller;

		// pristup k tride DB
		$db = $this->parentModule->env->db; // ->metoda();
	}
}

Nyní si ukážeme, co se stane, když zavoláme následující kód:

$env = new Environment;

echo " ";
print_r($env);
echo " ";

echo " ";
print_r($env->debug->printDebug());
echo " ";

Oba bloky vypíšou skoro to stejné. Klíčové slovíčko *RECURSION* nám potvrdí, že objekty jsou vázány opravdu přes referenci.

Environment Object
(
    [db] => Db Object
        (
        )

    [debug] => Debug Object
        (
            [env] => Environment Object
 *RECURSION*
        )

    [module] => MasterAdminController Object
        (
            [env] => Environment Object
 *RECURSION*
            [localModule] => ArticleAdminController Object
                (
                    [parentModule] => MasterAdminController Object
 *RECURSION*
                )
        )
)

Debug Object
(
    [env] => Environment Object
        (
            [db] => Db Object
                (
                )

            [debug] => Debug Object
 *RECURSION*
            [module] => MasterAdminController Object
                (
                    [env] => Environment Object
 *RECURSION*
                    [localModule] => ArticleAdminController Object
                        (
                            [parentModule] => MasterAdminController Object
 *RECURSION*
                        )
                )
        )
)

Dědičnost

Abychom pochopili, jak správně využít dědičnost, budeme si muset vysvětlit některé pojmy a stejně tak si povědět o základním smyslu dědění tříd. Příklad bych opět demonstroval na kostře projektu, na které jsme si popsali skládání. Přeskočíme třídu prostředí a začneme rovnou moduly. Mějme tedy instanci modulu pro administraci, která pak načítá už konkrétní obsluhy jednotlivých funkčních celků.

Chceme-li spravovat například již zmíněné články, kategorie a produkty, jistá část skriptů bude vždy podobná až stejná. Jiné části se budou lišit, a to konkrétními bloky kódu, které v jiných modulech obsaženy nebudou. A tak si navrhneme jakousi centrální třídu, kterou dané moduly zdědí.

Tím vlastně docílíme stejné logiky každého skriptu. Rodičovská třída bude obsahovat metody a parametry společné pro každý modul, její potomci ji pak vždy rozšíří o metody sobě specifické. Tím nejen ušetříme spoustu řádků kódu, ale také donutíme sami sebe psát ten kód tak říkajíc "slušně". Budeme muset dodržovat předem stanovenou logiku.

Druhý případ využití dědičnosti je už mnohem jednodušší. Máme knihovnu, do které potřebujeme přidat funkčnost. A jelikož knihovny nejsou určeny k tomu, aby se do nich hrabalo, založíme třídu novou, která knihovnu rozšíří. Můžeme vzít databázovou třídu a přidáme funkce pro zasílání chyb na e-mail. Tento způsob se často využívá v situacích, kdy jsou knihovny umístěné v nějakém centrálním úložišti, my je známe, můžeme je includovat ale nemůžeme tyto soubory měnit.

Na následujícím příkladě si ukážeme stav první. Ale zpět k důležitým pojmům. Než se dostanu ke kódu, je potřeba si tyto pojmy vysvětlit.

Viditelnost

PHP nabízí tři úrovně viditelnosti: public, protected a private. Public (veřejný) znamená, že k metodám či atributům třídy můžeme přistupovat z venčí. Protected (chráněný) toto omezí pouze na potomky: k funkci nepůjde přistupovat z venčí, ale pokud třídu zdědíme, ke všemu se dostaneme. Často se říká, že pokud chceme metody respektive atributy využít jen interně, dáme všechno protected. Nejvyšší stupeň utajení, private (soukromý), pak omezí přístup pouze na danou třídu. I když ji zdědíme, k metodám se nedostaneme.

Abstrakce

Klíčové slovo abstract definuje, že třída musí být zděděná. Nelze vytvořit její instance, jakýkoli pokus selže chybou. Pakliže je třída definovaná jako abstraktní, může obsahovat i abstrakní metody. Abstraktní metoda má pouze definici, nemá vlastní tělo funkce. Tím řekneme, že potomek musí danou funkci obsahovat. Jaké bude její tělo už ale neurčujeme. Asi by bylo dobré v daném kontextu i vysvětlit rozdíl mezi rozhraním (interface) a abstraktní třídou.

Rozhraní je šablona vzoru, osnova. Abstraktní třída už může být knihovna nebo modul, musí se ale "dokončit" jejím zděděním a dopsáním prázdných metod.

Klíčové slovo "final"

Naopak klíčové slovíčko final jakoukoli dědičnost zakazuje. Třída už je kompletní a nechce být žádným způsobem rozšiřována.

abstract class AdminController {

	public $parentModule;

	protected $path = array();

	private $someParam;

	final public function __construct($parent_module = null){
		$this->parentModule = $parent_module;
	}

	public function loadData($param){
		echo 'load some data';
	}

	abstract public function __init();
}

class CategoryAdminController extends AdminController {
	public function __init(){
		// samotne spusteni
	}

	public function loadData($param = null){
		parent::loadData($param);
		echo ' and do something';
	}

	public function loadAnotherData($param){
		echo 'load another data';
	}
}

$cont = new CategoryAdminController();
$cont->loadData();

Na výše uvedeném příkladu je ukázaná možnost redefinovat metody rodiče v potomkovi, což je přesně ten typ rozšíření, kvůli kterému vůbec chceme něco dědit. Ukázka vypíše přesně toto:

load some data and do something

Na závěr přikládám použitý kód ke stažení, i když si myslím, že zde to ani tolik nutné není.

Odkaz:

dedeni_skladani_sample.zip