Metoda fabrykująca zalicza się do wzorców kreacyjnych i jak sama nazwa mówi – musi coś tworzyć. Z tego wzorca korzystamy wtedy, kiedy nie mamy pewności jakie obiekty będą nam potrzebne lub potrzebujemy elastycznego, skalowalnego interfejsu do tworzenia obiektów. Metoda fabrykująca zwraca obiekty różnych klas, ale powiązanych typów.
SCHEMAT WZORCA METODY FABRYKUJĄCEJ
Źródło: pl.wikipedia.org/wiki/Metoda_wytworcza_(wzorzec_projektowy)
OPIS WZORCA
Product – to interfejs, który implementuje konkretna klasa ConcreteProduct:
1 2 3 4 5 |
interface Product { // public function doSomething(); // ... } |
1 2 3 4 5 6 7 8 |
class ConcreteProduct implements Product { // public function doSomething() { // echo 'Hello!'; // } // ... } |
Creator – to klasa abstrakcyjna fabryki, zawiera metodę fabrykującą factoryMethod(), która będzie „coś” tworzyć:
1 2 3 4 |
abstract class Creator { public abstract function factoryMethod(); } |
Ta metoda musi być oczywiście abstrakcyjna, jak sama klasa – zgodnie z definicją 🙂
Konkretna fabryka ConcreteCreator implementuje metodę fabrykującą factoryMethod() do wytworzenia konkretnego produktu – new ConcreteProduct:
1 2 3 4 5 6 7 8 |
class ConcreteCreator extends Creator { public function factoryMethod() { $product = new ConcreteProduct(); // return $product->doSomething(); } } |
Oczywiście wzorzec nabierze sensu i znaczenia, kiedy będziemy rozbudowywać nasz program tworząc kolejne obiekty:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// konkretne produkty: class ConcreteProduct_1 implements Product { ... } class ConcreteProduct_2 implements Product { ... } class ConcreteProduct_3 implements Product { ... } // ... // konkretne fabryki wytwarzające konkretne produkty: class ConcreteCreator_1 extends Creator { ... } class ConcreteCreator_2 extends Creator { ... } class ConcreteCreator_3 extends Creator { ... } // ... |
Użycie metody fabrykującej w klasie Client:
1 2 3 4 5 6 7 8 9 10 11 12 |
class Client { private $product; public function __construct() { $this->product = new ConcreteCreator(); echo $this->product->factoryMethod(); } } new Client(); |
Jak widać, klasa Client wysyła żądanie do konkretnej fabryki, a nie do konkretnych produktów. Tak więc produkty zostawiamy w spokoju, a wytwarzaniem obiektów zajmuje się fabryka i metoda fabrykująca 🙂 Jest to bardzo elastyczne podejście ułatwiające rozbudowę programu, a także proces wprowadzania zmian do niego, bez naruszania logiki.
PRZYKŁAD
Dla przykładu zbudujemy menu do strony internetowej. Na początek wystarczy standardowa nawigacja w nagłówku i w stopce strony. W przyszłości będziemy mieli zamiar dodać drzewo kategorii (zapewne generowane dynamicznie) z różnymi wariantami. Dlatego zastosowanie wzorca metody fabrykującej, w tym przypadku jest jak najbardziej uzasadnione.
Myśląc o menu wyobrażamy sobie interfejs 🙂
Interfejs Menu musi posiadać metodę, która będzie zwracać linki:
1 2 3 4 5 6 7 8 9 10 |
<?php // Menu.php interface Menu { public function getLinks(); } ?> |
Następnie tworzymy klasy, które zaimplementują interfejs Menu:
– TopMenu (menu w nagłówku strony):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<?php // TopMenu.php include_once('Menu.php'); class TopMenu implements Menu { private $menu; // metoda zwraca kod html konkretnego menu public function getLinks() { $this->menu.= '<ul>'; $this->menu.= '<li><a href="#home">Home</a></li>'; $this->menu.= '<li><a href="#about">About Us</a></li>'; $this->menu.= '<li><a href="#blog">Blog</a></li>'; $this->menu.= '<li><a href="#contact">Contact</a></li>'; $this->menu.= '<ul>'; return $this->menu; } } ?> |
– BottomMenu (menu w stopce strony):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<?php // BottomMenu.php include_once('Menu.php'); class BottomMenu implements Menu { private $menu; public function getLinks() { $this->menu.= '<a href="#cookies">Cookies Policy</a>'; $this->menu.= '<a href="#rodo">RODO</a>'; $this->menu.= '<a href="#contact">Contact</a>'; return $this->menu; } } ?> |
Metoda getLinks() w obu klasach musi być użyta, zgodnie z definicją interfejsu i zwraca różne linki oraz kod html.
Teraz musimy stworzyć fabrykę Creator z metodą fabrykującą. Metoda factoryMethod() jest abstrakcyjna i będzie mogła w dowolny sposób implementować konkretne produkty: TopMenu i BottomMenu. W przypadku tej metody zastosujemy enkapsulację wprowadzając modyfikator dostępu protected. Konkretna metoda factoryMethod() z fabryki, „przepuszczana” jest przez funkcję navigation().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?php // Creator.php abstract class Creator { protected abstract function factoryMethod(); public function navigation() { $nav = $this->factoryMethod(); return $nav; } } ?> |
Tworzymy konkretną fabrykę TopMenuCreator, która dziedziczy po fabryce Creator metodę fabrykującą factoryMethod>(). Metoda ta zmienia swoją postać na konkretną i implementuje instancję produktu TopMenu:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?php // TopMenuCreator.php include_once('Creator.php'); include_once('TopMenu.php'); class TopMenuCreator extends Creator { protected function factoryMethod() { $nav = new TopMenu(); return $nav->getLinks(); } } ?> |
Analogicznie postępujemy z klasą BottomMenuCreator:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?php // BottomMenuCreator.php include_once('Creator.php'); include_once('BottomMenu.php'); class BottomMenuCreator extends Creator { protected function factoryMethod() { $nav = new BottomMenu(); return $nav->getLinks(); } } ?> |
Na koniec tworzymy plik index.php, który pozwoli nam zobaczyć efekt naszej pracy 🙂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<?php // index.php include_once('TopMenuCreator.php'); include_once('BottomMenuCreator.php'); $top = new TopMenuCreator(); $bottom = new BottomMenuCreator(); ?> <!DOCTYPE HTML> <html> <head> <title>Factory Method</title> </head> <body> <header> <?php echo $top->navigation(); ?> </header> <section> Under construction ... </section> <footer> <?php echo $bottom->navigation(); ?> </footer> </body> </html> |