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 instancje obiektów.
Spójrz na ten przykład.
Factory Method : Edit z 2022
Mam klasę punkt, która ma swoje współrzędne X i Y.
public class Point
{
protected Point(double x, double y)
{
X = x;
Y = y;
}
public double X { get; }
public double Y { get; }
public override string ToString()
{
return $"X: {X}, Y: {Y}";
}
}
Punkty można stworzyć na wiele sposobów. Dla ułatwienia możemy wewnątrz tej klasy stworzyć metody Fabryki, które utworzą specyficzny punkt dla danych parametrów. Oto takie metody.
public class Point
{
public static Point NewCartesianPoint(double x,
double y)
{
return new Point(x, y);
}
public static Point NewPolarPoint(double rho,
double theta)
{
return new Point(rho * Math.Cos(theta), rho * Math.Sin(theta));
}
Dzięki tym fabrykom możemy tworzysz punkty lepiej.
Point p1 = Point.NewCartesianPoint(20, 40);
Point p2 = Point.NewPolarPoint(90, 90);
Console.WriteLine(p1);
Console.WriteLine(p2);
Możemy też wydzielić fabrykę do oddzielnej klasy. Dla porządku tę klasę możemy umieścić wewnątrz klasy Point.
public class Point2
{
public static class Factory
{
public static Point2 NewCartesianPoint(double x, double y)
{
return new Point2(x, y);
}
public static Point2 NewPolarPoint(double rho, double theta)
{
return new Point2(rho * Math.Cos(theta), rho * Math.Sin(theta));
}
}
protected Point2(double x, double y)
{
X = x;
Y = y;
}
public double X { get; }
public double Y { get; }
public override string ToString()
{
return $"X: {X}, Y: {Y}";
}
}
Oto użycie nowego kodu:
var point = Point2.Factory.NewCartesianPoint(20, 30);
Korzystając ze słowa partial można by było wydzielić kod fabryki i punktu do dwóch klas
public partial class Point3
{
public static class Factory
{
public static Point3 NewCartesianPoint(double x, double y)
{
return new Point3(x, y);
}
public static Point3 NewPolarPoint(double rho, double theta)
{
return new Point3(rho * Math.Cos(theta), rho * Math.Sin(theta));
}
}
}
public partial class Point3
{
protected Point3(double x, double y)
{
X = x;
Y = y;
}
public double X { get; }
public double Y { get; }
public override string ToString()
{
return $"X: {X}, Y: {Y}";
}
}
Ten styl wzorca farbki jest użyteczny do dzisiaj. Bądźmy jednak szczerzy, gdy pada słowo fabryka to mówimy o klasycznym podejściu do tego wzorca
Co jest z nim nie tak? Co można zrobić lepiej? Jak go użyć w prawdziwej aplikacji ASP.NET Core?
Abstract Factory
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.
Edit z 2022 roku:
Oto przykład użycia wzorca fabryki w ASP.NET Core w .NET 6. Stworzę sobie fabrykę jako delegatę generyczną i zarejestruje ją jako singleton do kontenera wstrzykiwania zależności.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<Func<Type, ISolider>>
(
provider =>
{
return new Func<Type, ISolider>(
(type) =>
{
if (type == Type.Samurai)
return CreateSamurai();
return CreateNinja();
}
);
}
);
var app = builder.Build();
Oto metody, do których ta fabryka się odwołuje :
Samurai CreateSamurai()
{
return new Samurai();
}
Ninja CreateNinja()
{
return new Ninja();
}
enum Type
{
Samurai,
Ninja
}
Oto użycie tej zadeklarowanej fabryki jako odnośnik do tamtej funkcji.
int number = 0;
app.MapGet("/", (Func<Type, ISolider> func) =>
{
var sol = func(Type.Samurai);
number++;
return $"Solider nr : {number} " +
$"\n\t Kosztuje : {sol.Price()}" +
$"\n\t Walczy : {sol.Fight()}";
});
app.MapGet("/n", (Func<Type, ISolider> func) =>
{
var sol = func(Type.Ninja);
number++;
return $"Solider nr : {number} " +
$"\n\t Kosztuje : {sol.Price()}" +
$"\n\t Walczy : {sol.Fight()}";
});
app.Run();
Dla odpowiedni adresów stworze odpowiedniego żołnierza. Pracuje obecnie nad umieszczenie wszystkich przykładów wzorców do jednego projektu. Kod z tego wpisu znajduje się już w tym projekcie: