/ Gists / Navrhove vzory - Bohmer

Gists - Navrhove vzory - Bohmer

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

0. Návrhové vzory - Bohmer - přehled vzorů

Navrhove vzory - Bohmer

pic.md #


On gists

1. Základní aplikace

Navrhove vzory - Bohmer

app.php #

<?php

interface Publication 
{
    public function open();
    public function close();
    public function setPageNumber($page);
    public function getPageNumber();
}


class Book implements Publication
{
    protected $category;
    protected $pageCount;
    protected $pageNumber = 0;
    protected $closed = TRUE;

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

    public function open()
    {
        $this->closed = FALSE;
    }

    public function setPageNumber($page)
    {
        if ($this->close !== FALSE &&$this->pageCount < $page)
        {
            return FALSE;
        }

        $this->pageNumber = $page;
        
        return TRUE;
    }

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

    public function close()
    {
        $this->setPageNumber(0);
        $this->closed = TRUE;
    }

    public function __destruct()
    {
        if (!$this->closed)
        {
            $this->close();
        }
    }

    public function getDailyRate($days = 1)
    {
        if ($days > 14)
            return 7.5;

        return 10;
    }

    public function getCategory()
    {
        return $this->category;
    }

}


class Member
{
    protected $id;
    protected $name;

    public function __construct($id, $name)
    {
        $this->id = $id;
        $this->name = $name;
    }

    public function getId()
    {
        return $this->id;
    }

    public function getName()
    {
        return $this->name;
    }
}


class Library
{
    protected $library = [];
    protected $rentalActions = [];
    protected $debugger;

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

    public function addToLibrary($id, Publication $p)
    {
        $this->library[$id] = $p;

        $this->debug('Nová knižka v knihovně ' . $p->getCategory());
    }

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

        if (!$publicationId)
            throw new \Exception('Tato publikace neexistuje');

        if (!$this->isPublicationAvailable($p))
            throw new \Exception('Tuto publikaci si uz nekdo pujcil');

        $this->debug('' . $m->getName() . ' si půjčil knihu ' . $p->getCategory());

        $rentalAction = new RentalAction($p, $m);
        $this->rentalActions[] = $rentalAction;

        return $rentalAction;
    }

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

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

            $this->debug($rentalAction->getMember()->getName() . '  vrátil knihu ' . $p->getCategory());
            $rentalAction->markPublicationReturned();
            return FALSE;
        }

        return TRUE;
    }

    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 getLibrary()
    {
        return $this->library;
    }
    
    public function getRentalActions()
    {
        return $this->rentalActions;
    }

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


class RentalAction
{
    protected $publication;
    protected $member;
    protected $rentDate;
    protected $returnDate = NULL;

    public function __construct(Publication $p, Member $m, $date = NULL)
    {
        $this->publication = $p;
        $this->member = $m;

        if ($date == NULL)
            $date = date('Y-m-d H:i:s');

        $this->rentDate = $date;
    }

	/**
	 * 
	 * @return mixed
	 */
	function getPublication() {
		return $this->publication;
	}
	
	/**
	 * 
	 * @return mixed
	 */
	function getMember() {
		return $this->member;
	}
	
	/**
	 * 
	 * @return mixed
	 */
	function getRentDate() {
		return $this->rentDate;
	}
	
	/**
	 * 
	 * @return mixed
	 */
	function getReturnDate() {
		return $this->returnDate;
    }
    

    public function markPublicationReturned($date = NULL)
    {
        if ($date == NULL)
            $date = date('Y-m-d H:i:s');

        $this->returnDate = $date;        
    }


    public function isReturned()
    {
        return $this->returnDate !== NULL;
    }
}

// DEBUGGER, kompozice
interface Debugger
{
    public function debug($message);
}

class DebuggerEcho implements Debugger
{
    public function debug($message)
    {
        echo $message . PHP_EOL;
    }
}


class DebuggerLog implements Debugger
{
    // zapis do souboru, pro demonstraci jen s prefixem ...
    public function debug($message)
    {
        echo 'LOG: ' . $message . PHP_EOL;
    }
}

class DebuggerNull implements Debugger
{
    public function debug($message)
    {
        
    }
}


$library = new Library(new DebuggerEcho);
$book1 = new Book('pc', 100);
$member1 = new Member(1, 'Roman Janko');
$member2 = new Member(2, 'Pepa Kos');

$library->addToLibrary(1, $book1);
$library->rentPublication($book1, $member1);
$library->returnPublication($book1);

$library->rentPublication($book1, $member2);
$library->returnPublication($book1);

$library->rentPublication($book1, $member1);
$library->returnPublication($book1);

$library->rentPublication($book1, $member1);


// print_r($library->getLibrary());
// print_r($library->getRentalActions());

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.');

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

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

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

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

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');