Паттерн проектирования «Фабричный метод»

В этом рецепте мы на простом примере покажем, как легко и быстро приготовить паттерн «Фабричный метод» (в английском варианте — Factory method), а чтобы не ходить вокруг и около в качестве примера возьмем уже готовую идею из книги Э.Фримен «Паттерны проектирования» и создадим свою сеть пиццерий, которые размещаются в разных регионах.

В этом примере от нас требуется создать сеть пиццерий, для каждого заведения из которой существует практически унифицированный рецепт приготовления пиццы, однако, в рецепте разных регионов существуют небольшие различия, которые также надо учесть. В нашем случае, есть два региона — это Нью-Йорк и Чикаго, которые хоть и следуют общим идеям в приготовлении пиццы, но жители предпочитают разную подачу и некоторые ингредиенты очень специфичны по местности.

Налицо проблема: нужно создавать объекты с практически одинаковой стратегией поведения, но небольшой разницей в каких-то компонентах, причем эта разница может накапливаться. Требуется организовать нечто вроде фабрики, которая создает уже целые пиццерии, настроенные под конкретную местность.

Проблему можно разрешить перейдя на более высокий уровень абстракции, для чего можно реализовать паттерн «Фабричный метод», официальное определение которого звучит так:

«Паттерн Фабричный метод определяет интерфейс создания объекта, но позволяет субклассам выбрать класс создаваемого экземпляра. Таким образом, Фабричный метод делегирует операцию создания экземпляра субклассам.»

Паттерн оперирует своей терминологией, которая впрочем используется нечасто: продукт — нечто, определяющее интерфейс объектов, создаваемых абстрактным методом; конкретный продукт — нечто, реализующее интерфейс продукта; создатель — нечто, что возвращает продукт и может вызывать фабричные методы создания продукта; конкретный продукт — нечто, что способно переопределить фабричные методы создателя под формирование конкретного продукта.

Сам паттерн в виде UML-диаграммы:

Полный код примера с паттерном (комментарии и юнит-тестирование включены):

module factorymethod;

/* Пример паттерна проектирования "Фабричный метод"
 * 
 * Краткое описание ситуации:
 *        Требуется создать сеть пиццерий, которая имеет практически унифицированные рецепт 
 *        приготовления пиццы, однако, должна учитывать и региональные различия в используемых
 *        компонентах и некоторых фазах рецепта.
 * 
 * Официальное определение паттерна:
 *       "Паттерн Фабричный метод определяет интерфейс создания объекта, но позволяет
 *        субклассам выбрать класс создаваемого экземпляра. Таким образом, Фабричный
 *        метод делегирует операцию создания экземпляра субклассам."
 * 
 * 
 *  Мои пояснения: 
 *        Часть кода заккоментирована, так как я понятия не имею, из каких ингредиентов
 *        и по какому рецепту делаются остальные виды пиццы.
 */


// общий класс для различных видов пиццерий
public abstract class PizzaStore
{
	// рецепт приготовления пиццы прописан тут
	public Pizza orderPizza(string type)
	{
		Pizza pizza;

		pizza = createPizza(type);

		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();

		return pizza;
	}

	// вот эта штука и называется фабричным методом
	protected abstract Pizza createPizza(string type);
}


// нью-йоркская пиццерия
public class NYPizzaStore : PizzaStore
{
	override Pizza createPizza(string item)
	{
		switch(item)
		{
			case "cheese":
				return new NYStyleCheesePizza();
				//			case "veggie":
				//				return NYStyleVeggiePizza();
				//			case "clam":
				//				return NYStyleClamPizza();
				//			case "pepperoni":
				//				return NYStylePepperoniPizza();
			default:
				return null;
		}
	}
}


// чикагская пиццерия
public class ChicagoPizzaStore : PizzaStore
{
	override Pizza createPizza(string item)
	{
		switch(item)
		{
			case "cheese":
				return new ChicagoStyleCheesePizza();
				//			case "veggie":
				//				return ChicagoStyleVeggiePizza();
				//			case "clam":
				//				return ChicagoStyleClamPizza();
				//			case "pepperoni":
				//				return ChicagoStylePepperoniPizza();
			default:
				return null;
		}
	}
}

import std.stdio;


// общий класс для всех типов пицц
public abstract class Pizza
{
protected:
	string name; // название пиццы
	string dough; // тип основы
	string sauce; // соус
	string[] toppings;  // добавки

public:
	// приготовление пиццы
	void prepare()
	{
		writeln("Preparing " ~ name);
		writeln("Tossing dough...");
		writeln("Adding toppings:");
		try
		{
			foreach (topping; toppings)
			{
				writeln("    " ~ topping);
			}
		}
		catch (Throwable)
		{

		}
	}

	
	// выпекание пиццы
	void bake()
	{
		writeln("Bake for 25 minutes at 350 degrees");
	}

	
	// нарезание пиццы
	void cut()
	{
		writeln("Cutting the pizza into diagonal slices");
	}

	
	// упаковка пиццы
	void box()
	{
		writeln("Place pizza in official PizzaStore Box");
	}

	
	// получить название пиццы
	string getName()
	{
		return name;
	}
}


// нью-йоркская пицца с сыром: тонкая основа, соус маринара, сыр реджано
public class NYStyleCheesePizza : Pizza
{
	this()
	{
		name = "NY Style Sauce and Cheese Pizza";
		dough = "Thin Crust Dough";
		sauce = "Marinara Sauce";
		toppings ~= "Grated Reggiano Cheese";
	}
}


// чикагская пицца с сыром: толстая основа, томатный соус, много сыра моццарелла
public class ChicagoStyleCheesePizza : Pizza
{
	this()
	{
		name = "Chicago Style Deep Dish Cheese Pizza";
		dough = "Extra Thick Crust Dough";
		sauce = "Plum Tomato Sauce";
		toppings ~= "Shredded Mozzarella Cheese";
	}

	// в Чикаго пиццу нарезают квадратиками
	override void cut()
	{
		writeln("Cutting the pizza into square slices");
	}
}


unittest
{
	writeln("--- Factory Method test ---");
	PizzaStore nyStore = new NYPizzaStore();
	PizzaStore chicagoStore = new ChicagoPizzaStore();

	Pizza pizza = nyStore.orderPizza("cheese");
	writeln();
	pizza = chicagoStore.orderPizza("cheese");
}

Плюсы использования:

  • паттерн позволяет сделать код создания объектов более универсальным, не привязываясь к конкретным классам, оперируя лишь общим интерфейсом;
  • позволяет установить связь между параллельными иерархиями классов

Недостатки паттерна:

  • необходимость создавать наследника для каждого нового типа продукта
  • большой объем кода

Полностью код и другие примеры паттернов доступны в нашем репозитории реализаций паттернов.

Добавить комментарий