FactoryWzór.15 Cześć witaj ponownie we wpisie na temat wzorów projektowych. Oprócz opisywania znanych wzorców projektowych chce także dać swoje 5 groszy i wyjaśnić, że nawet książkowe wzorce projektowe we współczesnym świecie mają lepsze alternatywy.

Dziś spojrzymy na wzorzec projektowy Fabryki.

Po co na fabryka, która ma wypluwać określone instancję obiektów.

Spójrz na ten przykład.

interface ISolider
{
    int Price();
    string Fight();
}

class Samurai : ISolider
{
    public string Fight() => "Clash";
    public int Price() => 33;
}

class Ninja : ISolider
{
    public string Fight() => "Slash";
    public int Price() => 21;
}

Teraz powiedzmy, że chcemy napisać metodę, która stworzy określonego żołnierza i karze mu się walczyć. Nie ma jednak możliwości dodania konstruktora do interfejsu "ISoldier". 

Potrzebujemy więc oddzielnej klasy Fabryki dla każdego naszego żołnierza. 

interface ISoliderFactory
{
    ISolider CreateSolider();
}

class SamuraiFactory : ISoliderFactory
{
    public ISolider CreateSolider() => new Samurai();
}

class NinjaFactory : ISoliderFactory
{
    public ISolider CreateSolider() => new Ninja();
}

W praktyce użylibyśmy wzorca fabryki w taki sposób.

static void Main(string[] args)
{
    Run(1, new NinjaFactory());
    Run(2, new SamuraiFactory());
}

static void Run(int number, ISoliderFactory factory)
{
    var sol = factory.CreateSolider();

    Console.WriteLine($"Solider nr : {number} " +
        $"\n\t Kosztuje : {sol.Price()}" +
        $"\n\t Walczy : {sol.Fight()}");
}

To działa w porządku. Wiesz mi, ale w kodzie produkcyjnym zobaczysz jedną fabrykę. Z tego wzorca korzysta się, gdy na przykład potrzebujesz tylko w jednym momencie obiektu, który zarządza połączeniem do bazy danych. Jeśli chciałbyś stylu tworzenia połączenia zmienić to nie ma problemu wstrzykujesz inną implementację fabryki.

Gdy raz na jakiś czas potrzebujesz obiektu, który zarządza połączeniami sieciowymi to dlaczego tego obiektu nie tworzy przy pomocy fabryki. 

Jedyne o czym musisz pamiętać to fakt, że obiekty utworzone przez twoją farbkę do takich celów muszą być przez Ciebie posprzątane.

Istnieje jednak jeden problem z tym wzorcem.

Gdyby chciał dodać nowego żołnierza to potrzebuje także nowej fabryki. Dla każdej nowej klasy potrzebuje także nowej fabryki. 

Czy można zrobić to lepiej?

Jeśli znasz wszystkie klasy z wyprzedzeniem to możesz stworzyć jedną klasę fabryki:

static class SoliderFactory2
{
    public static ISolider Create(Type type)
    {
        ISolider sol = null;
        switch (type)
        {
            case Type.Ninja:
                sol = new Ninja();
                break;
            case Type.Samurai:
                sol = new Samurai();
                break;
        }

        return sol;
    }
}

Te rozwiązanie wymusza na tobie utworzenie odpowiednio typu wyliczeniowego:

public enum Type
{
    Ninja,
    Samurai
}

Problem tworzenia dodatkowych klas tak naprawdę nie zniknął tylko przeniósł się do jednego typu wyliczeniowego. 

Funkcje Fabryki

Programowanie funkcyjne ma na celu wyeliminowania klas spełniających cel tworzenia innych klas.

Zamiast przekazywać Interfejs IsoldierFactory do metody Run...

Może lepiej jest przekazać delegatę generyczną Func<ISoldier>

static void Run(int number, Func<ISolider> factoryFunc)
{
    var sol = factoryFunc();

    Console.WriteLine($"Solider nr : {number} " +
        $"\n\t Kosztuje : {sol.Price()}" +
        $"\n\t Walczy : {sol.Fight()}");
}

Teraz do metody Run() przekazujemy odpowiednie wyrażenia lambda

static void Main(string[] args)
{
    Run(1, () => new Ninja());
    Run(2, () => new Samurai());
}

Wynik naszego działania jest taki sam. Korzystając funkcji wyeliminowaliśmy potrzebę tworzenia całej gamy klas 

Jedyny problem, który widzę, że gdybyś chciał wstrzykiwać logikę tworzenia obiektów to wciąż może byś wolał korzystać z interfejsów. Chociaż gotowe implementacje delegat też można wstrzykiwać. 

services.AddSingleton(provider => 
new Func<IUnitOfWork>(() => provider.GetService<IUnitOfWork>()));

W końcu w swoim repozytorium kodu albo posiadasz różne fabryki do tworzenia obiektów, albo posiadasz zaszyte funkcje do tworzenia tych obiektów.

To od Ciebie zależy co wyda się łatwiejsze do rozumienia.