PHP je věda

Objektově orientované programování nejen v PHP

Abstraktní třídy a dědičnost

Pojem dědičnost snad každý programátor zná, ne každý však ví, k čemu lze dědičnost prakticky využít. Dědičnost označuje technickou možnost vytvořit pomocí slovíčka extend novou třídu, která má stejné vlastnosti a metody jako třída rodičovská.

<?php
 
Class MyAuthenticationService extends AuthenticationService {
 
    public function loginFromJson($jsonString) {
        // implementace metody
    }
 
}

Ano, samozřejmě, toto je dědičnost. Ala jaký to má vlastně význam? Metodu loginFromJson bychom úplně stejně mohli vložit již do třídy AuthenticationService. V takovémto případě nám dědičnost moc nepomůže. Dědičnost získává na významu především s využitím návrhových vzorů, ke kterým se dostanu později. Zatím předvedu obecný příklad s abstraktní třídou, kde si můžeme snadno přiblížit smysluplné využití dědičnosti.

Představme si, že máme implementovat třídu Downloader, která stahuje data z xml rozhranní, nebo z json rozranní.

<?php
abstract class UserDownloader
{
 
    /**
     * @param $userId
     *
     * @return User
     * @throws Exception
     */
    public function downloadUser($userId)
    {
        $contents = file_get_contents($this->getUrl() . $userId);
        If (!$contents) {
            Throw new Exception("no contents foud!");
        }
        return $this->parseDataToUser($contents);
    }
 
    /**
     * @param $contents
     *
     * @return User
     */
    abstract protected function parseDataToUser($contents);
 
    /**
     * @return string
     */
    abstract protected function getUrl();
}

Příklad je jednoduchý, snažíme se o to, abychom si ušetřili vícenásobné volání metody file_get_contents a kontrolu, zda se povedlo nějaká data stáhnout. Přitom však víme, že na straně poskytovatele dat mohou být data v xml formátu, nebo v json formátu. Vytvoříme tedy dvě třídy XmlDownloader a JsonDownloader.

<?php
class XmlDownloader extends UserDownloader
{
 
    /**
     * @param $contents
     *
     * @return User
     */
    protected function parseDataToUser($contents)
    {
        $xmlData = new SimpleXMLElement($contents);
        return new User($xmlData["name"], $xmlData["password"], $xmlData["id"]);
    }
 
    /**
     * @return string
     */
    protected function getUrl()
    {
        return "http://www.mydomain.com/xmldata?id=";
    }
}
 
class JsonDownloader extends UserDownloader
{
 
    /**
     * @param $contents
     *
     * @return mixed|User
     */
    Protected function parseDataToUser($contents)
    {
        $data = json_decode($contents);
        Return new User($data["nam"], $data["passw"], $data["id"]);
    }
 
    /**
     * @return string
     */
    protected function getUrl()
    {
        return "http://www.mydomain.com/jsondata?id=";
    }
}

Můžeme následovně vytvořit instanci třídy podle toho, z jakého rozhraní chceme stahovat data.

<?php
$jsonDownloader = new JsonDownloader();
$user           = $jsonDownloader->downloadUser(11);
 
$xmlDownloader  = new XmlDownloader();
$user           = $xmlDownloader->downloadUser(11);

Doufám, že každý vidí zcela jasně užitek, který nám dědičnost v tomto případě poskytuje. Jenom jednou je potřeba implementovat stahovací logiku a následovně konkrétní implementace – podle toho zda se jedná o XML nebo json – se starají o to, aby ze stažených dat vytvořili ten správný objekt. Pokud si představíme, že by byla logika metody UserDownloader::downloadUser složitější a šlo by opravdu jen o to, stažená data správně namapovat na objekt typu User je zde dědičnost nepostradatelný pomocník.