Зачем нужен?
Паттерн Abstract factory применяется для инкапсуляции зависимостей(операционная система, базы данных и тд). Клиент используют интерфейс, предоставляемый фабрикой, а конкретная реализация(конкретная фабрика) определяется на этапе исполнения.
Обеспечивает соблюдение принципа инверсии зависимостей. Уменьшает связанность классов.
Определение
- предоставляет интерфейс создания семейства взаимосвязанных или взаимозависимых объектов без указания их конкретных классов
Реализация
Объект фабрики берет на себя ответственность за создание сервисов для всего семейства продуктов. Клиенты не создают объекты напрямую, а делегируют создание фабрике. Таким образом, для создания объектов используется композиция.
Реализация использует фабричный метод для каждого продукта. Каждый фабричный метод инкапсулирует new
оператор и конкретные, платформо-зависимые классы.
- Спроектируйте интерфейс семеств продуктов, для каждого объекта свой фабричный метод
- Определите конкретную фабрику для каждого семейства зависимых продуктов
- Клиент должен использовать фабричные методы конкретной фабрики для создания продуктов, конкретную фабрику получает через инициализатор(используется делегация)
Пример
Допустим у нас есть сеть пиццерий по всей стране. В Нью-Йорке жители любят тонкий слой теста, одни специи. В Чикаго - толстый слой теста, другие специи.
Мы хотим стандартизировать ингредиенты с учетом того, что в разных регионах могут использоваться разные соусы и тд. Таким образом наименование ингредиентов остается одинаковым, а реализация - разной.
Для этого объявим общий интерфейс семейств продуктов(набор фабричных методов):
// абстрактная фабрика
protocol PizzaIngredientFactory {
func createDough() -> Dough
func createSauce() -> Sauce
func createCheese() -> Cheese
func createVeggies() -> [Veggies]
func createPepperoni() -> Pepperoni
func createClam() -> Clams
}
И реализуем конкретные фабрики. Обратите внимание, что реализация ингредиентов отличается.
// конкретная фабрика
class NYPizzaIngredientFactory: PizzaIngredientFactory {
func createDough() -> Dough {
return ThinCrustDough()
}
func createSauce() -> Sauce {
return MarinaraSauce()
}
func createCheese() -> Cheese {
return ReggianoCheese()
}
func createVeggies() -> [Veggies] {
let veggies: [Veggies] = [Garlic(), Onion(), Mushroom(), RedPepper()]
return veggies
}
func createPepperoni() -> Pepperoni {
return SlicedPepperoni()
}
func createClam() -> Clams {
return FreshClams()
}
}
// конкретная фабрика
class ChikagoPizzaIngredientFactory: PizzaIngredientFactory {
func createDough() -> Dough {
return ThinCrustDough()
}
func createSauce() -> Sauce {
return MarinaraSauce()
}
func createCheese() -> Cheese {
return ReggianoCheese()
}
func createVeggies() -> [Veggies] {
let veggies: [Veggies] = [Garlic(), Onion(), Mushroom(), RedPepper()]
return veggies
}
func createPepperoni() -> Pepperoni {
return SlicedPepperoni()
}
func createClam() -> Clams {
return FreshClams()
}
}
Определим абстрактного клиента
// абстрактные продукт
protocol Pizza {
var name: String! { get set }
var dough: Dough! { get }
var sauce: Sauce! { get }
var veggies: [Veggies]? { get }
var cheese: Cheese? { get }
var pepperoni: Pepperoni? { get }
var clam: Clams? { get }
// абстрактный метод
func prepare()
func bake()
func cut()
func box()
}
extension Pizza {
func bake() {
print("Bake for 25 minutes at 350")
}
func cut() {
print("Cutting the pizza into diagonal slices")
}
func box() {
print("Place pizza in official PizzaStore box")
Реализуем конкретных клиентов. Конкретную фабрику получаем через инициализатор, реализацию prepare
делегируем фабрике
// конкретный продукт
class CheesePizza: Pizza {
var name: String!
var dough: Dough!
var sauce: Sauce!
var veggies: [Veggies]?
var cheese: Cheese?
var pepperoni: Pepperoni?
var clam: Clams?
var ingredientFactory: PizzaIngredientFactory
init(ingredientFactory: PizzaIngredientFactory) {
self.ingredientFactory = ingredientFactory
}
func prepare() {
print("Preparing \(name)")
dough = ingredientFactory.createDough()
sauce = ingredientFactory.createSauce()
cheese = ingredientFactory.createCheese()
}
}
// конкретный продукт с другими ингредиентами
class ClamPizza: Pizza {
var name: String!
var dough: Dough!
var sauce: Sauce!
var veggies: [Veggies]?
var cheese: Cheese?
var pepperoni: Pepperoni?
var clam: Clams?
var ingredientFactory: PizzaIngredientFactory
init(ingredientFactory: PizzaIngredientFactory) {
self.ingredientFactory = ingredientFactory
}
func prepare() {
print("Preparing \(name)")
dough = ingredientFactory.createDough()
sauce = ingredientFactory.createSauce()
cheese = ingredientFactory.createCheese()
// У Нью-Йоркской фабрики мидии будут свежими, у Чикагской - мороженными
// но наши классы от этого не зависят - данные аспекты определяются фабрикой
clam = ingredientFactory.createClam()
}
}
Создадим экземпляр конкреткой фабрики и будем передавать его клиентам
// абстрактный класс создатель
protocol PizzaStore {
func orderPizza(type: String) -> Pizza
// фабричный метод
func createPizza(type: String) -> Pizza
}
extension PizzaStore {
func orderPizza(type: String) -> Pizza {
// для создание пиццы используем фабричный метод
let pizza = createPizza(type)
pizza.prepare()
pizza.bake()
pizza.cut()
pizza.box()
return pizza
}
}
// конкретный класс-создатель
class NYPizzaStore: PizzaStore {
func createPizza(type: String) -> Pizza {
let ingredientFactory = NYPizzaIngredientFactory()
let newPizza: Pizza
switch type {
case "cheese":
newPizza = CheesePizza(ingredientFactory: ingredientFactory)
newPizza.name = "New York Style Cheese Pizza"
case "clam":
newPizza = ClamPizza(ingredientFactory: ingredientFactory)
newPizza.name = "New York Style Clam Pizza"
default:
newPizza = CheesePizza(ingredientFactory: ingredientFactory)
newPizza.name = "New York Style Cheese Pizza"
}
return newPizza
}
}
// конкретный класс-создатель
class ChikagoPizzaStore: PizzaStore {
// fabric method
func createPizza(type: String) -> Pizza {
let ingredientFactory: PizzaIngredientFactory = ChikagoPizzaIngredientFactory()
let newPizza: Pizza
switch type {
case "cheese":
newPizza = CheesePizza(ingredientFactory: ingredientFactory)
newPizza.name = "Chikago Style Cheese Pizza"
case "clam":
newPizza = ClamPizza(ingredientFactory: ingredientFactory)
newPizza.name = "Chikago Style Clam Pizza"
default:
newPizza = CheesePizza(ingredientFactory: ingredientFactory)
newPizza.name = "Chikago Style Cheese Pizza"
}
return newPizza
}
}
let nyStore = NYPizzaStore()
let chikagoStore = ChikagoPizzaStore()
var pizza: Pizza
pizza = nyStore.orderPizza("cheese")
print(pizza.name)
\*
Preparing New York Style Cheese Pizza
Bake for 25 minutes at 350
Cutting the pizza into diagonal slices
Place pizza in official PizzaStore box
New York Style Cheese Pizza
*\
print("\n")
pizza = chikagoStore.orderPizza("cheese")
print(pizza.name)
\*
Preparing Chikago Style Cheese Pizza
Bake for 25 minutes at 350
Cutting the pizza into diagonal slices
Place pizza in official PizzaStore box
Chikago Style Cheese Pizza
*\