Породжуючі шаблони описують створення (instantiate) об'єкта або групи пов'язаних об'єктів.
Спочатку створюються абстрактні класи. З їх допомогою легше виявити та задати потрібні типи, виразити типи як збірні поняття інших типів. Описуються інтерфейси взаємодії з кожним типом. Абстрактно (без реалізації) описуються процеси створення екземплярів цих типів та варіанти їх використання через наявні інтерфейси.
Після визначення типів, опису інтерфейсів та абстрагування процесів створення, можна приступити до вибору структур даних та алгоритмів для їх обробки, а також до реалізації конкретних класів.
Коли створення об'єкта містить певну логіку, а не просто присвоєння значень, доцільно делегувати це завдання спеціалізованій фабриці.
Реалізація фабрики для створення різних типів качок:
using System;
// Проста фабрика для створення качок
public class DuckFactory
{
public Duck CreateDuck(string type)
{
switch (type.ToLower())
{
case "mallard":
return new MallardDuck();
case "model":
return new ModelDuck();
case "rubber":
return new RubberDuck();
default:
throw new ArgumentException($"Невідомий тип качки: {type}");
}
}
}
// Використання фабрики
class Program
{
static void Main(string[] args)
{
DuckFactory factory = new DuckFactory();
Duck mallard = factory.CreateDuck("mallard");
Duck model = factory.CreateDuck("model");
Duck rubber = factory.CreateDuck("rubber");
Console.WriteLine("Створено качку: " + mallard.Display);
Console.WriteLine("Створено качку: " + model.Display);
Console.WriteLine("Створено качку: " + rubber.Display);
}
}
Розширена система з регіональними варіантами качок:
// Абстрактний клас з фабричним методом
public abstract class DuckCreator
{
// Фабричний метод
public abstract Duck CreateDuck();
public void DisplayDuckInfo()
{
Duck duck = CreateDuck();
Console.WriteLine($"Створено качку: {duck.Display}");
Console.WriteLine($"Звук: {duck.PerformQuack()}");
Console.WriteLine($"Політ: {duck.PerformFly()}");
}
}
// Конкретні фабрики для різних типів качок
public class MallardDuckCreator : DuckCreator
{
public override Duck CreateDuck() => new MallardDuck();
}
public class ModelDuckCreator : DuckCreator
{
public override Duck CreateDuck() => new ModelDuck();
}
public class RubberDuckCreator : DuckCreator
{
public override Duck CreateDuck() => new RubberDuck();
}
// Використання фабричного методу
class Program
{
static void Main(string[] args)
{
DuckCreator[] creators = {
new MallardDuckCreator(),
new ModelDuckCreator(),
new RubberDuckCreator()
};
foreach (var creator in creators)
{
creator.DisplayDuckInfo();
Console.WriteLine();
}
}
}
| Критерій | Проста Фабрика | Фабричний метод |
|---|---|---|
| Інкапсуляція | Весь код в одному методі | Розподілений по підкласах |
| Розширюваність | Потрібно змінювати фабрику | Додаємо нові підкласи |
| Складність | Простіша реалізація | Складніша структура |
| Використання | Коли об'єктів небагато і рідко змінюються | Коли система повинна бути розширюваною |
| Принцип SOLID | Може порушувати OCP | Дотримується OCP |
Просту фабрику варто використовувати для простих випадків, коли кількість типів об'єктів невелика і рідко змінюється. Фабричний метод краще підходить для складних систем, де потрібна гнучкість і розширюваність.
Створимо систему для автоматизації роботи кав'ярні:
public enum CoffeeType {
ESPRESSO,
AMERICANO,
CAFFE_LATTE,
CAPPUCCINO
}
public class CoffeeFactory {
public Coffee createCoffee(CoffeeType type) {
switch(type) {
case ESPRESSO: return new Espresso();
case AMERICANO: return new Americano();
case CAFFE_LATTE: return new CaffeLatte();
case CAPPUCCINO: return new Cappuccino();
default: throw new IllegalArgumentException("Unknown coffee type");
}
}
}
Розширимо наш приклад для підтримки різних стилів кав'ярень (італійський, американський).
public abstract class CoffeeShop {
public abstract Coffee createCoffee(CoffeeType type);
public Coffee orderCoffee(CoffeeType type) {
Coffee coffee = createCoffee(type);
coffee.grindCoffee();
coffee.makeCoffee();
coffee.pourIntoCup();
return coffee;
}
}
public class ItalianCoffeeShop extends CoffeeShop {
@Override
public Coffee createCoffee(CoffeeType type) {
switch(type) {
case ESPRESSO: return new ItalianEspresso();
// інші типи...
}
}
}