/ Gists

Gists

On gists

9. Bridge

Navrhove vzory - Bohmer

bridge.php #

<?php

include "1.php";

/*
    Návrhový vzor Bridge odděluje rozhraní třídy od její vlastní implementace a umožňuje
    provádět jejich změnu nezávisle na sobě.
*/


/*
    Definice
    --------
    Návrhový vzor Bridge odděluje rozhraní třídy od její vlastní implementace a umožňuje
    provádět jejich změnu nezávisle na sobě.
    Pro dosažení tohoto cíle je nutné provést následující kroky:
    1. Definovat rozhraní nezbytné pro jednotlivé implementace.
    2. Vytvořit minimálně dvě třídy implementující rozhraní z bodu 1.
    3. Vytvořit způsob, jak abstrakci předat konkrétní implementaci.
    4. V abstrakci využívat služby implementace z bodu 2 přes rozhraní definované
    v bodě 1.


  Shrnutí
  -------
  Návrhový vzor Bridge odděluje abstrakci od její konkrétní implementace a zamezuje
  tak zbytečnému nárůstu počtu tříd při předávání implementací. Tento
  návrhový vzor řeší podobný problém jako návrhový vzor Adapter, představený
  v předchozí kapitole. Rozdíl mezi těmito dvěma vzory je především v tom, že
  vzor Bridge se používá již při návrhu aplikace, zatímco vzor Adapter řeší problém,
  jak zabezpečit komunikaci již hotových tříd.
*/

interface Storage 
{
    public function insertPublication($id, Publication $p);
    public function fetchPublications();
}


class StorageArray implements Storage
{
    protected $publications;

    public function insertPublication($id, Publication $p)
    {
        $this->publications[$id] = $p;
    }

    public function fetchPublications()
    {
        return $this->publications;
    }
}

// Upravena trida library oproti 1 dilu
abstract class AbstractLibrary
{
    protected $rentalActions = array();
    protected $debugger;
    protected $storage;

    public function __construct(Debugger $debugger, Storage $storage)
    {
        $this->debugger = $debugger;
        $this->storage = $storage;
    }

    protected function debug($message)
    {
        $this->debugger->debug($message);
    }

    public function rentPublication(Publication $p, Member $m)
    {
        $publicationId = array_search($p, $this->getPublications());

        if (false === $publicationId) {
            throw new UnknownPublicationException();
        }

        if (!$this->isPublicationAvailable($p)) {
            throw new PublicationNotAvailableException();
        }

        $rentalAction = new RentalAction($p, $m);
        $this->rentalActions[] = $rentalAction;
        $this->debug(
            $m->getName() . ' si vypůjčil publikaci: ' . $p->getCategory()
        );

        return $rentalAction;
    }

    public function returnPublication(Publication $p)
    {
        foreach ($this->rentalActions as $rentalAction) 
        {
            if ($rentalAction->getPublication() !== $p) {
                continue;
            }
            if ($rentalAction->isReturned()) {
                continue;
            }

            $rentalAction->markPublicationReturned();
            $this->debug(
                $rentalAction->getMember()->getName() .
                    ' vrátil publikaci: ' .
                    $p->getCategory()
            );

            return true;
        }

        return false;
    }

    public function isPublicationAvailable(Publication $p)
    {
        foreach ($this->rentalActions as $rentalAction) 
        {
            if ($rentalAction->getPublication() !== $p) {
                continue;
            }

            if ($rentalAction->isReturned()) {
                continue;
            }

            return false;
        }

        return true;
    }

    public function generateId()
    {
        $publications = $this->getPublications();
        return 'PUBLICATION-' . count($publications);
    }

    public function getPublications()
    {
        return $this->storage->fetchPublications();
    }

    abstract public function addToLibrary($id, Publication $p);
}


class Library extends AbstractLibrary
{
    public function addToLibrary($id, Publication $p)
    {
        $this->storage->insertPublication($id, $p);
        $this->debug('Nová publikace v knihovně: ' . $p->getCategory());
    }
}


// Storages
class StorageDatabase implements Storage
{
    protected $db;
    public function __construct($filename)
    {
        $this->db = new \SQLiteDatabase($filename, 0666);
        $this->initialiseDatabase();
    }
    public function insertPublication($id, Publication $p)
    {
        // INSERT TO DB
    }
    public function fetchPublications()
    {
       // SELECT FROM DB && FETCH to $publications (array or ...)
       return $publications
    }
    
    protected function initialiseDatabase() 
    {
     // connection to db
    }
}



class StorageArray implements Storage
{
    protected $publications;

    public function insertPublication($id, Publication $p)
    {
        $this->publications[$id] = $p;
    }

    public function fetchPublications()
    {
        return $this->publications;
    }
}


// USAGE

$publication1 = new Book('PC', 450);
$publication2 = new Book('MEDICINA', 50);

$storage = new StorageDatabase('library.sqlite');
$debugger = DebuggerEcho::getInstance();

$library = new Library($debugger, $storage);
$library->addToLibrary($library->generateId(), $publication1);
$library->addToLibrary($library->generateId(), $publication2);
$publications = $library->getPublications();

print_r($publications);

On gists

8. Adapter

Navrhove vzory - Bohmer

adapter.php #

<?php

include "1.php";

/*
    Návrhový vzor Adapter přizpůsobí rozhraní určité třídy rozhraní požadovanému
    klientem a umožní tak spolupráci tříd, které by kvůli nekompatibilním rozhraním
    nebyly schopné spolupracovat.


    Definice
    ---------
    Návrhový vzor Adapter přizpůsobí rozhraní určité třídy rozhraní požadovanému
    klientem a umožní tak spolupráci tříd, které by kvůli nekompatibilním rozhraním
    nebyly schopné spolupracovat.
    Pro vytvoření adaptéru, který umožní spolupráci dvou nekompatibilních rozhraní,
    je nutné provést následující kroky:
    1. Lokalizovat rozdíly mezi nabízeným a požadovaným rozhraním.
    2. Implementovat novou třídu, která bude poskytovat požadované rozhraní.
    3. Vytvořit způsob, jak předat adaptéru objekt, který má být přizpůsobený
    – například pomocí principu vkládání závislostí (Dependency Injection).
    4. Implementovat všechny metody vyžadované rozhraním a delegovat požadavky
    dále na příslušné metody původního objektu.
    5. Přihlížet k signalizování chyb.
    6. V aplikaci využívat objekt adaptéru a pomocí něho „obalit“ původní objekt.

*/




class Title
{
    const INFO_CATEGORY = 'category';
    const INFO_PAGECOUNT = 'pageCount';
    const TITLE_OPEN = 1;
    const TITLE_CLOSE = 0;

    protected $info = array();
    protected $currentPage = 0;
    protected $state = self::TITLE_CLOSE;

    public function __construct($pageCount, $category)
    {
        $this->info[self::INFO_PAGECOUNT] = $pageCount;
        $this->info[self::INFO_CATEGORY] = $category;
    }

    public function getInfo($info)
    {
        if (isset($this->info[$info])) {
            return $this->info[$info];
        }
    }

    public function setClosed($state)
    {
        $this->state = $state;
    }

    public function getClosed()
    {
        return $this->state;
    }

    public function setCurrentPage($page)
    {
        if (self::TITLE_CLOSE == $this->state) {
            $this->setClosed(self::TITLE_OPEN);
        }
        $this->currentPage = $page;
    }

    public function getCurrentPage()
    {
        if (self::TITLE_CLOSE == $this->state) {
            throw new ClosedException('Kniha není otevřena');
        }
        return $this->currentPage;
    }
}


// Lisi se nazvy metod, parametry atd ...
$title = new Title(100, 'PC');
$title->setCurrentPage(47);
printf(
"Kategorie: %s\n", $title->getInfo(Title::INFO_CATEGORY)
);
printf(
"Počet stran: %d\n", $title->getInfo(Title::INFO_PAGECOUNT)
);
printf(
"Kniha je otevřena na %d. straně\n", $title->getCurrentPage()
);


$book = new Book('PC', 100);
$book->open();
$book->setPageNumber(47);
printf(
"Kategorie: %s\n", $book->getCategory()
);
printf(
"Počet stran: %d\n", $book->getPageCount()
);
printf(
"Kniha je otevřena na %d. straně\n", $book->getPageNumber()
);

// OUTPUT

/*

Kategorie: PC
Počet stran: 100
Kniha je otevřena na 47. straně

Kategorie: PC
Počet stran: 100
Kniha je otevřena na 47. straně

*/

On gists

7. Composite

Navrhove vzory - Bohmer

composite.php #

<?php

<?php

include "1.php";

/*
    Návrhový vzor Composite spojuje více objektů do stromové struktury, v níž se navenek
    tváří jako jeden objekt a lze je tak i použít.

    ---

    Spojení objektů do stromové struktury znamená následující:
    1. Vytvořit možnost poskládat více objektů typu Debugger do stromové struktury.
    2. Objekt, který tuto stromovou strukturu obsahuje, se musí chovat jako jeden
    z objektů typu Debugger.
*/

/* 

    Návrhový vzor Composite spojuje více objektů do stromové struktury, v níž se navenek
    tváří jako jeden objekt a lze je tak i použít.
    Pro dosažení tohoto cíle je nutné provést následující kroky:
    
    1. V případě, že tak ještě nebylo učiněné, definovat rozhraní pro samotný
    objekt stromu.
    2. Vytvořit novou třídu, která implementuje toto rozhraní. Do této třídy přidat
    další metodu (například addChild()), které se budou předávat libovolné
    objekty implementující rozhraní z bodu 1. Tyto předané objekty následně
    uložit jako pole v atributu třídy.
    3. Implementovat všechny metody vyžadované rozhraním z bodu 1 takovým
    způsobem, že se prochází přes všechny objekty přidané do třídy v bodě 2
    a postupně je na ně delegováno volání příslušné metody.
    4. Vytvořit libovolné objekty a pomocí metody addChild() je přiřadit do stromové
    struktury.
    5. V aplikaci použít tento strom objektů místo jednotlivých objektů.

*/


class DebuggerComposite implements Debugger
{
    protected $debuggers = array();

    public function addDebugger(Debugger $debugger)
    {
        $this->debuggers[] = $debugger;
    }

    public function removeDebugger(Debugger $debugger)
    {
        $key = array_search($debugger, $this->debuggers);
        if (false === $key) {
            return false;
        }
        unset($this->debuggers[$key]);
        return true;
    }
    
    public function debug($message)
    {
        foreach ($this->debuggers as $debugger) {
            $debugger->debug($message);
        }
    }
}

// Jednodussi pouziti
$debuggerEcho = DebuggerEcho::getInstance();
$debuggerLog = DebuggerLog::getInstance('./library.log');

$composite = new DebuggerComposite();
$composite->addDebugger($debuggerEcho);
$composite->addDebugger($debuggerLog);

$debuggerEcho->debug('Výpis na monitor');
$debuggerLog->debug('Zápis do souboru');
$composite->debug('Monitor + Soubor');

On gists

6. Prototype

Navrhove vzory - Bohmer

prototype.php #

<?php

/*
    Návrhový vzor, který vám pomůže zamezit přemnožení tříd, se nazývá Prototype
    (Prototyp).
    Návrhový vzor Prototype určuje způsoby vytváření objektů pomocí prototypu objektu,
    který se při tvorbě nové instance zkopíruje (naklonuje).
*/

/*
    Definice 
    Návrhový vzor Prototype určuje způsoby vytváření objektů pomocí prototypu objektu,
    který se při tvorbě nové instance zkopíruje (naklonuje).
    K dosažení tohoto cíle je nutné provést následující kroky:
    1. Implementovat „správce prototypů“, který všechny prototypy, jež jsou
    v sy sté mu dostupné, uloží a zpřístupní. Tento správce musí poskytovat způsob,
    jak uložit nové prototypy pod určitým označením a následně je pod
    tímto označením zpřístupnit.
    2. V případě potřeby ve třídách, z nichž se budou vytvářet prototypy, implementovat
    metodu __clone(). Toto je nutné jen v případě, kdy prototypy
    obsahují reference na další objekty, které je nutné při klonování také klonovat.
    Pokud se takovéto reference v objektu nenacházejí, potom není nutné
    metodu __clone() implementovat.
    3. Vytvořit a inicializovat všechny prototypy, jež lze v systému využít, a předat
    je správci prototypů.
*/

class Book implements Publication
{
    // ... atributy třídy

    protected $cd = false;
    protected $symbol = null;
    // ... metody třídy
    public function setCd($cd)
    {
        $this->cd = $cd;
    }
    public function hasCd()
    {
        return $this->cd;
    }
    public function setSymbol($symbol)
    {
        $this->symbol = $symbol;
    }
    public function getSymbol()
    {
        return $this->symbol;
    }
}


class SpecialEditionPublisher 
{
    protected $prototype = array();

    public function addSpecialEdition($edition, Publication $pr) 
    {
        $this->prototype[$edition] = $pr;
    }

    public function publishPublication($edition)
    {
        if (!isset($this->prototype[$edition])) {
            throw new UnknownSpecialEditionException(
                'No prototype for special edition "' . $edition . '" registered.'
            );
        }
        return clone $this->prototype[$edition]; // CLONE 
    }
    
}

// [1]
$bookAlaItalia = new Book('italská kuchyně', 280);
$bookAlaItalia->setSymbol('Itálie');
$bookAlaItalia->setCd(true);
// klon
$bookAlaItalia2 = clone $bookAlaItalia;

// [2]
$bookAlaItalia = new Book('italská kuchyně', 280);
$bookAlaItalia->setSymbol('Itálie');
$bookAlaItalia->setCd(true);

$journalCars = new Journal('auto-moto', 100);
$journalCars->setSymbol('Auto');
$journalCars->setCd(false);

$publisher = new SpecialEditionPublisher();
$publisher->addSpecialEdition('A La Italia', $bookAlaItalia);
$publisher->addSpecialEdition('Vozidla 20. století', $journalCars);

// Debug
$book1 = $publisher->publishPublication('A La Italia');
print 'Typ: ' . get_class($book1) . "\n";
print 'Kategorie: ' . $book1->getCategory() . "\n";
print 'Počet stran: ' . $book1->getPageCount() . "\n";
print 'Symbol: ' . $book1->getSymbol() . "\n";
print 'CD: ' . ($book1->hasCd() ? 'ano' : 'ne') . "\n";

$car1 = $publisher->publishPublication('Vozidla 20. století');
print 'Typ: ' . get_class($car1) . "\n";
print 'Kategorie: ' . $car1->getCategory() . "\n";
print 'Počet stran: ' . $car1->getPageCount() . "\n";
print 'Symbol: ' . $car1->getSymbol() . "\n";
print 'CD: ' . ($car1->hasCd() ? 'ano' : 'ne') . "\n";

/*
    Typ: cz\k1886\publications\Book
    Kategorie: italská kuchyně
    Počet stran: 280
    Symbol: Itálie
    CD: ano
    
    -----------------------------------

    Typ: cz\k1886\publications\Journal
    Kategorie: auto-moto
    Počet stran: 100
    Symbol: Auto
    CD: ne
*/


// Klonovani mimo properties
/*
    Návrhový vzor Prototype vyžaduje, aby všechny vaše třídy, z nichž se mají vytvářet
    prototypy, šlo klonovat. To se na prvý pohled může jevit jednodušší, než jaké
    je to ve skutečnosti. Obsahují-li vaše prototypy v atributech jen skalární hodnoty,
    pak stačí použít operátor clone. Jsou-li však v atributech těchto tříd uložené
    reference na další objekty, musíte implementovat metodu __clone(), která se
    postará o vytvoření jejich kopií.
*/

class CD
{
    protected $track = 1;
    
    public function getTrack()
    {
        return $this->track;
    }

    public function setTrack($track)
    {
        $this->track = $track;
    }
}


class Book implements Publication
{
    // ... atributy třídy
    protected $cd;

    // ... metody třídy
    public function getCd()
    {
        return $this->cd;
    }

    public function setCd(CD $cd)
    {
        $this->cd = $cd;
    }
}


$bookAlaItalia = new Book('italská kuchyně', 280);
$bookAlaItalia->setSymbol('Itálie');
$bookAlaItalia->setCd(new CD());

$publisher = new SpecialEditionPublisher();
$publisher->addSpecialEdition('A La Italia', $bookAlaItalia);
$book1 = $publisher->publishPublication('A La Italia');
$book2 = $publisher->publishPublication('A La Italia');

print 'Aktuální stopa na CD v $book1: '
. $book1->getCd()->getTrack() . "\n";
print 'Aktuální stopa na CD v $book2: '
. $book2->getCd()->getTrack() . "\n";

/*
Aktuální stopa na CD v $book1: 1
Aktuální stopa na CD v $book2: 1
*/

$book2->getCd()->setTrack(5);

print 'Aktuální stopa na CD v $book1: '
. $book1->getCd()->getTrack() . "\n";
print 'Aktuální stopa na CD v $book2: '
. $book2->getCd()->getTrack() . "\n";

/*
Aktuální stopa na CD v $book1: 5
Aktuální stopa na CD v $book2: 5
*/

class Book implements Publication {
    // ... atributy a metody třídy
    public function __clone() 
    {
        // Thats the trick!
        $cd = clone $this->getCd();
        $this->setCd($cd);
    }
}

On gists

5. Builder

Navrhove vzory - Bohmer

builder.php #

<?php

include "1.php";

/*
Návrhový vzor Builder odděluje konstrukci komplexních objektů od jejich reprezentace
tak, aby se dal stejný konstrukční proces použít při tvorbě různých reprezentací.
*/

/* 
    DEFINICE:
    Návrhový vzor Builder odděluje konstrukci komplexních objektů od jejich reprezentace
    tak, aby se dal stejný konstrukční proces použít při tvorbě různých reprezentací.
    Pro dosažení tohoto cíle je nutné provést následující kroky:
    1. Definovat třídu reprezentující výsledný produkt.
    2. Definovat rozhraní stavitele, jenž bude schopný sestrojit stranu z bodu 1.
    3. Vytvořit minimálně dvě konkrétní implementace rozhraní z bodu 2.
    4. Definovat třídu, která pomocí stavitele z bodu 3 řídí sestrojení produktu.
*/

/*
    ULOHA:

    1. Definovat třídu reprezentující jednu stranu knihy.
    2. Definovat rozhraní stavitele, jenž bude schopný sestrojit stranu z bodu 1.
    3. Vytvořit stavitele, kteří sestrojí první a poslední stranu knihy.
    4. Definovat třídu sazeče, který pomocí stavitelů z bodu 3 řídí sestrojení jednotlivých
    stran.

*/

class Page
{
    protected $header;
    protected $body;
    protected $footer;

    public function getHeader()
    {
        return $this->header;
    }

    public function setHeader($header)
    {
        $this->header = $header;
    }

    public function getBody()
    {
        return $this->body;
    }

    public function setBody($body)
    {
        $this->body = $body;
    }

    public function getFooter()
    {
        return $this->footer;
    }

    public function setFooter($footer)
    {
        $this->footer = $footer;
    }
}

interface PageBuilder
{
    public function getPage();
    public function createNewPage();
    public function buildHeader();
    public function buildBody();
    public function buildFooter();
}

abstract class AbstractPageBuilder implements PageBuilder
{
    protected $page;

    public function getPage()
    {
        return $this->page;
    }

    public function createNewPage()
    {
        $this->page = new Page();
    }
}

class FirstPageBuilder extends AbstractPageBuilder
{
    public function buildHeader()
    {
        $this->page->setHeader('Záhlaví první strany');
    }

    public function buildBody()
    {
        $this->page->setBody('Tělo první strany');
    }

    public function buildFooter()
    {
        $this->page->setFooter('Zápatí první strany');
    }
}

class LastPageBuilder extends AbstractPageBuilder
{
    public function buildHeader()
    {
        $this->page->setHeader('Záhlaví poslední strany');
    }

    public function buildBody()
    {
        $this->page->setBody('Tělo poslední strany');
    }
    
    public function buildFooter()
    {
        $this->page->setFooter('Zápatí poslední strany');
    }
}

// DIRECTOR
class Compositor
{
    protected $pageBuilder;

    public function setPageBuilder(PageBuilder $pageBuilder)
    {
        $this->pageBuilder = $pageBuilder;
    }

    public function getPage()
    {
        return $this->pageBuilder->getPage();
    }

    public function compositePage()
    {
        $this->pageBuilder->createNewPage();
        $this->pageBuilder->buildHeader();
        $this->pageBuilder->buildBody();
        $this->pageBuilder->buildFooter();
    }
}

// USAGE
$firstPageBuilder = new FirstPageBuilder();
$lastPageBuilder = new LastPageBuilder();

$compositor = new Compositor();
$compositor->setPageBuilder($firstPageBuilder);
$compositor->compositePage();
$firstPage = $compositor->getPage();
var_dump($firstPage);

$compositor->setPageBuilder($lastPageBuilder);
$compositor->compositePage();
$lastPage = $compositor->getPage();
var_dump($lastPage);


/*

C:\wamp64\www\Bohmer\5.php:150:
object(Page)[4]
  protected 'header' => string 'Záhlaví první strany' (length=23)
  protected 'body' => string 'Tělo první strany' (length=19)
  protected 'footer' => string 'Zápatí první strany' (length=22)
  
C:\wamp64\www\Bohmer\5.php:155:
object(Page)[5]
  protected 'header' => string 'Záhlaví poslední strany' (length=26)
  protected 'body' => string 'Tělo poslední strany' (length=22)
  protected 'footer' => string 'Zápatí poslední strany' (length=25)

*/

On gists

4. Abstract Factory (Abstraktní továrna)

Navrhove vzory - Bohmer

abstract-factory.php #

<?php

//include "1.php";

/*
    Pro vytvoření jednotlivých komponent bez nutnosti zadání jejich konkrétní implementace použijeme návrhový vzor Abstract Factory.
    Návrhový vzor Abstract Factory poskytuje rozhraní pro vytvoření skupin podobných nebo souvisejících objektů bez pojmenování konkrétních tříd.
*/

/* 
    Definice
    Návrhový vzor Abstract Factory poskytuje rozhraní pro vytvoření skupin podobných
    nebo souvisejících objektů bez pojmenování konkrétních tříd.
    K dosažení tohoto cíle je nutné provést následující kroky:
    1. Definovat rozhraní továrny, kde pro každý objekt ze skupiny objektů bude
    deklarovaná abstraktní metoda. K tomu lze použít buď abstraktní třídu,
    nebo rozhraní.
    2. Implementovat abstraktní třídy, jež reprezentují jednotlivé objekty ze skupiny
    objektů.
    3. Implementovat libovolné potomky těchto abstraktních tříd.
    4. Implementovat jednu nebo více tříd (továren), které tyto objekty vytvářejí.
*/

// ABSTRAKTNI TRIDY

abstract class Table
{
    protected $rows = array();
    protected $header = null;
    public function addRow(Row $row)
    {
        $this->rows[] = $row;
    }
    public function setHeader(Header $header)
    {
        $this->header = $header;
    }
    abstract public function show();
}


abstract class Row
{
    protected $cells = array();

    public function addCell(Cell $cell)
    {
        $this->cells[] = $cell;
    }

    abstract public function show();
}


abstract class Header extends Row 
{
}


abstract class Cell
{
    protected $content = null;
    public function __construct($content)
    {
        $this->content = $content;
    }

    abstract public function show();
}



// INTERFACE TABLE FACTORY
interface TableFactory
{
    public function createCell($content);
    public function createRow();
    public function createHeader();
    public function createTable();
}


class HtmlCell extends Cell
{
    public function show()
    {
        printf(" <td>%s</td>\n", $this->content);
    }
}

class HtmlRow extends Row
{
    public function show()
    {
        print " <tr>\n";
        foreach ($this->cells as $cell) {
            $cell->show();
        }
        print " \n";
    }
}

class HtmlHeader extends Header
{
    public function show()
    {
        print " <tr style=\"font-weight: bold;\">\n";

        foreach ($this->cells as $cell) {
            $cell->show();
        }

        print " </tr>\n";
    }
}

class HtmlTable extends Table
{
    public function show()
    {
        print "<table border=\"1\">\n";
        $this->header->show();
        foreach ($this->rows as $row) {
            $row->show();
        }
        print "\n";
    }
}

// USAGE
$table = new HtmlTable();
$header = new HtmlHeader();
$header->addCell(new HtmlCell('Sloupec 1'));
$header->addCell(new HtmlCell('Sloupec 2'));
$table->setHeader($header);

$row = new HtmlRow();
$row->addCell(new HtmlCell('Pozice [1:1]'));
$row->addCell(new HtmlCell('Pozice [1:2]'));
$table->addRow($row);
$table->show();

/*

<table border="1">
    <tr style="font-weight: bold;">
        <td>Sloupec 1</td>
        <td>Sloupec 2</td>
    </tr>
    <tr>
        <td>Pozice [1:1]</td>
        <td>Pozice [1:2]</td>
    </tr>
</table>

Tím jste splnili jednu část úlohy – dokážete vytvořit tabulku HTML, které může-
te z vnějšku předat obsah. Nové objekty jste ale vytvářeli pomocí operátoru new,
kvůli čemuž je kód pevně svázaný s konkrétní implementací. Pokud byste nyní
chtěli změnit tabulku HTML na textový výpis do konzoly, museli byste změnit
skoro každý řádek zdrojového kódu. Tomu se můžete vyhnout pomocí defino-
vaného rozhraní TableFactory. Je tedy nutné přidat další třídu, která toto roz-
hraní implementuje a vrátí konkrétní implementaci zodpovědnou za tabulku
HTML. Vytvořte tedy novou třídu HtmlTableFactory, která implementuje roz-

 */


// ABSTRACT FACTORY - OUR GOAL
class HtmlTableFactory implements TableFactory
{
    public function createCell($content)
    {
        return new HtmlCell($content);
    }
    public function createRow()
    {
        return new HtmlRow();
    }
    public function createHeader()
    {
        return new HtmlHeader();
    }
    public function createTable()
    {
        return new HtmlTable();
    }
}

// USAGE
$factory = new HtmlTableFactory();
$table = $factory->createTable();
$header = $factory->createHeader();
$header->addCell($factory->createCell('Sloupec 1'));
$header->addCell($factory->createCell('Sloupec 2'));
$table->setHeader($header);

$row = $factory->createRow();
$row->addCell($factory->createCell('Pozice [1:1]'));
$row->addCell($factory->createCell('Pozice [1:2]'));
$table->addRow($row);
$table->show();

/*
Po provedení tohoto skriptu dostanete naprosto stejný výsledek jako v předcho-
zím příkladu.
Jako poslední zbývá implementovat třídu, která vypíše seznam publikací knihov-
ny na základě údajů z pole. Tato třída má pro vytvoření elementů tabulky pou-
žít libovolnou třídu implementující rozhraní TableFactory. Tím oddělíte třídu,
která vytváří seznam, od zobrazení ve formě kódu jazyka HTML. Konkrétní
továrnu, která bude zobrazovat tabulku se seznamem, předáte nové třídě při
tvorbě její instance, přičemž znovu využijete princip vkládání závislostí (angl.
Dependency Injection).
*/

class PublicationList
{
    protected $tableFactory = null;
    public function __construct(TableFactory $tf)
    {
        $this->tableFactory = $tf;
    }
    public function displayTable($data)
    {
        $table = $this->tableFactory->createTable();

        $header = $this->tableFactory->createHeader();
        $header->addCell($this->tableFactory->createCell('Kategorie'));
        $header->addCell($this->tableFactory->createCell('Počet stran'));

        $table->setHeader($header);

        foreach ($data as $line) {
            $row = $this->tableFactory->createRow();
            foreach ($line as $field) {
                $cell = $this->tableFactory->createCell($field);
                $row->addCell($cell);
            }
            $table->addRow($row);
        }
        $table->show();
    }
}

// USAGE
$publications = array(
    array('PC', 100),
    array('PC', 380),
    array('medicína', 250),
    array('historie', 70)
);
$pl = new PublicationList(new HtmlTableFactory());

$pl->displayTable($publications);


/* ****************************************************************** */
// CLI, Textova tabulka pouze pro demonstraci


class TextCell extends Cell
{
    public function show()
    {
        $diff = strlen($this->content);
        mb_strlen($this->content, 'UTF-8');
        print '|' . str_pad($this->content, 15 + $diff);
    }
}

class TextRow extends Row
{
    public function show()
    {
        foreach ($this->cells as $cell) {
            $cell->show();
        }

        print "|\n";
        $str = '';
        for ($i = 0; $i < count($this->cells); $i++) {
            $str .= '+' . str_repeat('-', 15);
        }

        print $str . "+\n";
    }
}

class TextHeader extends Header
{
    public function show()
    {
        $str = '';
        for ($i = 0; $i < count($this->cells); $i++) {
            $str .= '+' . str_repeat('-', 15);
        }

        print $str . "+\n";

        foreach ($this->cells as $cell) {
            $cell->show();
        }

        print "|\n";
        print $str . "+\n";
    }
}

class TextTable extends Table
{
    public function show()
    {
        $this->header->show();
        foreach ($this->rows as $row) {
            $row->show();
        }
    }
}

class TextTableFactory implements TableFactory
{
    public function createCell($content)
    {
        return new TextCell($content);
    }
    public function createRow()
    {
        return new TextRow();
    }
    public function createHeader()
    {
        return new TextHeader();
    }
    public function createTable()
    {
        return new TextTable();
    }
}



$publications = array(
    array('PC', 100),
    array('PC', 380),
    array('medicína', 250),
    array('historie', 70)
);

$pl = new PublicationList(new TextTableFactory());
$pl->displayTable($publications);



// ZAVEREM
/*
Návrhový vzor Abstract Factory poskytuje rozhraní pro vytvoření skupin podobných nebo souvisejících objektů bez pojmenování konkrétních tříd.
*/



On gists

3. Static Factory Method

Navrhove vzory - Bohmer

static-factory-method.php #

<?php

class DebuggerFactory
{
    public static function createDebugger($type)
    {
        switch (strtolower($type)) {
            case 'log':
                return DebuggerLog::getInstance();
                break;
            case 'echo':
                return DebuggerEcho::getInstance();
                break;
            default:
                throw new UnknownDebuggerException();
        }
    }
}

On gists

3. Factory Method (Tovární metoda)

Navrhove vzory - Bohmer

factory-method.php #

<?php

/*
    Definice
    Návrhový vzor Factory Method definuje rozhraní pro vytváření objektů, 
    přičemž vlastní tvorbu instancí přenechává svým potomkům. Potomci rozhodují, které konkrétní implementace se mají použít.
*/


abstract class AbstractPublisher
{
    protected $category;

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

    public function sellPublication($pageCount)
    {
        $publication = $this->createPublication($pageCount);
        $publication->open();
        $page = mt_rand(1, $pageCount);
        $publication->setPageNumber($page);
        $publication->close();

        return $publication;
    }

    abstract protected function createPublication($pageCount);
}


class BookPublisher extends AbstractPublisher
{
    protected function createPublication($pageCount)
    {
        $publication = new Book($this->category, $pageCount);
        return $publication;
    }
}


class JournalPublisher extends AbstractPublisher
{
    protected function createPublication($pageCount)
    {
        // Journal je jako Book
        $publication = new Journal($this->category, $pageCount);
        return $publication;
    }
}


// Usage
$bookPublisher = new BookPublisher('PC');
$book = $bookPublisher->sellPublication(100);
print "Zakoupena nová publikace: \n";
printf("Typ: %s \n", get_class($book));
printf("Kategorie: %s \n", $book->getCategory());
printf("Počet stran: %d \n", $book->getPageCount());

$journalPublisher = new JournalPublisher('medicína');
$journal = $journalPublisher->sellPublication(20);
print "Zakoupena nová publikace: \n";
printf("Typ: %s \n", get_class($journal));
printf("Kategorie: %s \n", $journal->getCategory());
printf("Počet stran: %d \n", $journal->getPageCount());

On gists

0. Pravidla

Navrhove vzory - Bohmer

pravidla.md #

  1. Přístup k údajům vždy v rámci třídy zapouzdřete a poskytněte metody, pomocí nichž lze dané údaje získat.
  2. Svá rozhraní navrhujte tak, aby je bylo možné později rozšířit.
  3. V metodách tříd nezapouzdřujte jen údaje, ale také algoritmy, díky čemuž budou komplexní operace implementované centrálně na jednom místě.
  4. Znovupoužitelnost kódu je lepší než duplicitní kód.
  5. Vyvarujte se monolitickým strukturám a rozložte je na co nejmenší části, které mohou být implementované nezávisle na sobě. Pokud používáte rozsáhlé příkazy if/elseif/else nebo switch, popřemýšlejte, zda by se nedaly nahradit zaměnitelnými třídami.
  6. Dědění vede k neflexibilním strukturám. Na kombinaci různých funkcí používejte raději kompozice objektů.
  7. Vždy programujte vůči rozhraní, a nikdy ne vůči konkrétní implementaci.
  8. Vyhýbejte se těsným závislostem mezi jednotlivými třídami aplikace a vždy upřednostnujte volné vazby tříd.

On gists

2. Singleton

Navrhove vzory - Bohmer

singleton.php #

<?php

class DebuggerEcho implements Debugger 
{
  
  protected function __construct() {}
  private function __clone() {}
  
  public static function getInstance() 
  {
    if (null == self::$instance) 
    {
      self::$instance = new self();
    }
      return self::$instance;
  }
  
  public function debug($message) 
  {
    print $message . "\n";
  }
}

// AKA multition
class DebuggerLog implements Debugger
{
    protected $logfile = null;
    private static $instances = array();
    public static function getInstance($logfile)
    {
        if (!isset(self::$instances[$logfile])) {
            self::$instances[$logfile] = new self($logfile);
        }
        return self::$instances[$logfile];
    }
    protected function __construct($logfile)
    {
        $this->logfile = $logfile;
    }
    private function __clone()
    {
    }
    public function debug($message)
    {
        error_log($message . "\n", 3, $this->logfile);
    }
}

// usage
$debugger1 = DebuggerLog::getInstance('./debugger1.log');
$debugger1->debug('Lorem ipsum dolor sit amet.');
$debugger2 = DebuggerLog::getInstance('./debugger2.log');
$debugger2->debug('Proin fringilla bibendum sagittis.');
$debugger3 = DebuggerLog::getInstance('./debugger1.log');
$debugger3->debug('Mauris vitae augue dolor.');