Factory Method (Metoda Wytwórcza)

factory method uml

Opis

Wzorzec projektowy Factory Method należy do grupy wzorców konstrukcyjnych. Metoda Wytwórcza, jak każda z fabryk, jest odpowiedzialna za dostarczenie interfejsu do tworzenia obiektów. W tym wypadku dostarczany jest interfejs, ale już tworzenie konkretnego obiektu reprezentującego wspólny typ ma miejsce w podklasie.

Charakterystyczne dla tego wzorca projektowego jest to, że struktura klas i interfejsów produktu (czyli części wytwarzanej przez fabrykę) jest tożsama ze strukturą klas i interfejsów fabryk. Tym samym dla każdego powiązanego typu istnieje dokładnie jedna fabryka.

Problem i rozwiązanie

Metoda Wytwórcza rozwiązuje ten sam problem co Prosta Fabryka, ale we właściwy sposób, bo w tym celu wykorzystuje mechanizm programowania obiektowego – polimorfizm. Jeśli obiekt zostanie stworzony w złym miejscu to szybko okaże się, że występuje silne powiązanie z konkretnym typem przez co kod staje się ciężki w testowaniu i rozszerzaniu. Skoro gdzieś do konkretyzacji i tak musi dojść, to w części przypadków najbardziej odpowiednim do tego miejscem okaże się fabryka. Przy okazji bardziej skomplikowane kreacje zostaną zamknięte w fabrykach i będą mogły zostać ponownie użyte.

Plusy i minusy

Metoda Wytwórcza nie ma żadnych wad. No dobra – na siłę doszukałem się jednej. Rzeczywiście, jeżeli klas produktów będzie dużo to liczba fabryk także spuchnie, przez co złożoność rośnie. Ciężko mi się wypowiedzieć, bo nie miałem takiego przypadku. Myślę, że to rzadkość, bo przecież ile może być obiektów spełniających wspólny interfejs. Z drugiej strony, ta sama logika bez metody wytwórczej byłaby pewnie jeszcze mniej czytelna i trudniejsza w zarządzaniu. Lepiej mieć 50 czytelnych klas, niż jednego nieczytelnego switcha z 50 przypadkami.

Factory Method niesie ze sobą same korzyści. Żyje w zgodzie z pierwszymi dwoma zasadami SOLID, czyli pojedyncza odpowiedzialność (single responsibility) i otwarte-zamknięte (open-closed). Dobrze enkapsuluje logikę tworzenia obiektu oraz pozwala na jej wielokrotne użycie bez potrzeby duplikacji kodu. Dzięki temu, że działa na abstrakcji zapewnia dużą elastyczność.

Metoda Fabrykująca jest po prostu lepszą implementacją od Prostej Fabryki. Zawiera w sobie wszystkie jej dobrodziejstwa i dodatkowo przezwycięża jej największy minus, czyli łamanie zasady otwarte-zamknięte.

Przykładowa implementacja w PHP

Zaprezentowany mechanizm metody wytwórczej jest uniwersalny. Zazwyczaj w początkowej fazie dokładnie tak wygląda. Czasem jednak budowanie obiektów jest trochę bardziej skomplikowane i wymaga przekazania zależności do konstruktora. Myślę, że taka implementacja jest właściwa żeby zrozumieć, na czym polega ten wzorzec projektowy. Kolejne rozszerzenia powinny być już łatwe do dorzucenia.

MealFactoryInterface definiuje konktrakt dla fabryki, która potrafi stworzyć posiłek. Każdy z typów posiłków posiada swoją własną wytwórnie, która spełnia ten interfejs. Na ten moment istnieją dwa rodzaje posiłków: wegańskie i wegetariańskie.

<?php

declare(strict_types=1);

namespace DesignPatterns\Creational\FactoryMethod;

interface MealInterface
{
    public function containsAnimalProducts(): bool;
}
<?php

declare(strict_types=1);

namespace DesignPatterns\Creational\FactoryMethod;

final class VeganMeal implements MealInterface
{
    public function containsAnimalProducts(): false
    {
        return false;
    }
}
<?php

declare(strict_types=1);

namespace DesignPatterns\Creational\FactoryMethod;

final class VegetarianMeal implements MealInterface
{
    public function containsAnimalProducts(): true
    {
        return true;
    }
}
<?php

declare(strict_types=1);

namespace DesignPatterns\Creational\FactoryMethod;

interface MealFactoryInterface
{
    public function createMeal(): MealInterface;
}

<?php

declare(strict_types=1);

namespace DesignPatterns\Creational\FactoryMethod;

final class VeganMealFactory implements MealFactoryInterface
{
    public function createMeal(): MealInterface
    {
        return new VeganMeal();
    }
}
<?php

declare(strict_types=1);

namespace DesignPatterns\Creational\FactoryMethod;

final class VegetarianMealFactory implements MealFactoryInterface
{
    public function createMeal(): MealInterface
    {
        return new VegetarianMeal();
    }
}
<?php

declare(strict_types=1);

namespace DesignPatterns\Creational\FactoryMethod\Test;

use DesignPatterns\Creational\FactoryMethod\VeganMeal;
use DesignPatterns\Creational\FactoryMethod\VeganMealFactory;
use DesignPatterns\Creational\FactoryMethod\MealInterface;
use PHPUnit\Framework\TestCase;

final class VeganMealFactoryTest extends TestCase
{
    public function testCanCreateVeganMeal(): void
    {
        $meal = (new VeganMealFactory())->createMeal();

        self::assertInstanceOf(MealInterface::class, $meal);
        self::assertInstanceOf(VeganMeal::class, $meal);
    }
}
<?php

declare(strict_types=1);

namespace DesignPatterns\Creational\FactoryMethod\Test;

use DesignPatterns\Creational\FactoryMethod\VegetarianMeal;
use DesignPatterns\Creational\FactoryMethod\VegetarianMealFactory;
use DesignPatterns\Creational\FactoryMethod\MealInterface;
use PHPUnit\Framework\TestCase;

final class VegetarianMealFactoryTest extends TestCase
{
    public function testCanCreateVegetarianMeal(): void
    {
        $meal = (new VegetarianMealFactory())->createMeal();

        self::assertInstanceOf(MealInterface::class, $meal);
        self::assertInstanceOf(VegetarianMeal::class, $meal);
    }
}

Factory Method – podsumowanie

Metoda fabrykująca to esencja wzorców projektowych – posiada wiele zalet i praktycznie żadnych wad. Tak jak wspomniałem jest sensowniejszym mechanizmem, niż Simple Factory. Z tym, że ta druga jest dużo łatwiejsza w implementacji jeśli chodzi o zastany, słabo przemyślany kod. Przy tworzeniu od zera zazwyczaj szedłbym w tę pierwszą.

Spotkałem się z opinią, że przewagą Metody Wytwórczej jest możliwość wyniesienia wyżej i wyabstrachowania jakiegoś wspólnego zachowania. Albo czegoś nie rozumiem albo to wymyślona przewaga, bo w obu przypadkach można ten mechanizm zaimplementować. Dla mnie jedyna różnica, ale bardzo znacząca jest taka, że zamiast jednego wielkiego switcha i jednej metody, mamy dla każdej klasy dedykowaną wytwórnię.

Wzorzec Factory Method w PHP jest z powodzeniem wykorzystywany. Nie wymaga dużego poświęcenia, a jego użycie niesie ze sobą same plusy. Wspiera dobre praktyki i otwiera pole do użycia kolejnych wzorców projektowych.

Programista PHP i właściciel marki Koddlo. Pasjonat czystego kodu i dobrych praktyk programowania obiektowego. Prywatnie fan dobrego humoru i podcastów.