Swift Design Patterns: Factory Method

Зачем нужен?

Паттерн Factory method применяется когда создатель(фреймворк) должен стандартизировать архитектурную модель для клиентов и одновременно позволить клиентам определять свои объекты предметной области(domain objets).

Обеспечивает соблюдение принципа инверсии зависимостей. Уменьшает связанность классов.

Определение

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

Реализация

Суперкласс определяет стандартное поведение и делегирует детали создания субклассам, определенным пользователем. Для создания объектов используется наследование.

Существует несколько реализаций: статичный метод(вместо метода инициализации) или абстрактный метод(реализация определяется подклассами)

  1. Спроектируйте аргументы фабричного метода(по каким харектиристикам фабричный метод будет выбирать конкретный продукт)
  2. Объявите констуктуры приватными или защищенными
  3. Рассмотрите возможность создания внутреннего “object pool”, который позволит возвращать один и тот же экземпляр, вместо создания нового каждый раз

Пример

Фабричный метод похож на абстрактную фабрику, но в нем отсутствует акцент на семействах.

Допустим у нас есть сеть пиццерий по всей стране. В Нью-Йорке жители любят тонкий слой теста, немного специй. В Чикаго - толстый слой теста, много сыра.

Мы хотим, чтобы процесс приготовления был стандартизирован и одинаков во всех пиццерий, но при этом учитывались особенности местной кухни.

Для этого объявим общий интерфейс для пиццерий, при этом делегируем реализацию фабричного метода субклассам:

// абстрактный класс создатель
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 {
        switch type {
            case "cheese":
                return NYStyleCheesePizza()
            case "veggie":
                return NYStyleVeggiePizza()
            case "clam":
                return NYStyleClamPizza()
            case "pepperoni":
                return NYStylePepperoniPizza()
        default:
            return NYStyleCheesePizza()
        }
    }
}


// конкретный класс-создатель
class ChikagoPizzaStore: PizzaStore {
    
    func createPizza(type: String) -> Pizza {
        switch type {
        case "cheese":
            return ChikagoStyleCheesePizza()
        case "veggie":
            return ChikagoStyleVeggiePizza()
        case "clam":
            return ChikagoStyleClamPizza()
        case "pepperoni":
            return ChikagoStylePepperoniPizza()
        default:
            return ChikagoStyleCheesePizza()
        }
    }
}

Объявим абстрактный продукт и стандартизируем процесс приготовления

// абстрактный продукт
protocol Pizza {
    var name: String { get }
    var dough: String { get }
    var sauce: String { get }
    var toppings: [String] { get }
    
    func prepare()
    
    func bake()
    
    func cut()
    
    func box()
}

// добавляем реализацию по умолчанию
extension Pizza {
    
    func prepare() {
        print("Preparing \(name)")
        print("Tossing dough...")
        print("Adding sauce")
        print("Adding toppings \(toppings)")
    }
    
    func bake() {
        print("Bake for 25 minutes at 350")
    }
    
    func cut() {
        print("Cutting the pizza into diagonal slices")
    }
    
    func box() {
        print("Place pizza in the official PizzaStore box")
    }
}

Создадим конкретные продукты

// конкретные продукты
class NYStyleCheesePizza: Pizza {
    let name = "Ny style Sauce and Cheese Pizza"
    let dough = "Thin Crust Dough"
    let sauce = "Marinara Sauce"
    let toppings = ["Grated Reggiano Cheese"]
}

class NYStyleVeggiePizza: Pizza {
    let name = "Ny style Sauce and Veggie Pizza"
    let dough = "Thin Crust Dough"
    let sauce = "Marinara Sauce"
    let toppings = ["Veggie"]
}

class NYStyleClamPizza: Pizza {
    let name = "Ny style Sauce and Clam Pizza"
    let dough = "Thin Crust Dough"
    let sauce = "Marinara Sauce"
    let toppings = ["Clam"]
}

class NYStylePepperoniPizza: Pizza {
    let name = "Ny style Sauce and Pepperoni Pizza"
    let dough = "Thin Crust Dough"
    let sauce = "Marinara Sauce"
    let toppings = ["Pepperoni"]
}

class ChikagoStyleCheesePizza: Pizza {
    let name = "Chikago style Deep Dish Cheese Pizza"
    let dough = "Extra Thick Crust Dough"
    let sauce = "Plum Tomato Sauce"
    let toppings = ["Shredded Mozarella Cheese"]
    
    func cut() {
        print("Cutting pizza into square slices")
    }
}

class ChikagoStyleVeggiePizza: Pizza {
    let name = "Chikago style Deep Dish Veggie Pizza"
    let dough = "Extra Thick Crust Dough"
    let sauce = "Plum Tomato Sauce"
    let toppings = ["Veggie"]
    
    func cut() {
        print("Cutting pizza into square slices")
    }
}

class ChikagoStyleClamPizza: Pizza {
    let name = "Chikago style Deep Dish Clam Pizza"
    let dough = "Extra Thick Crust Dough"
    let sauce = "Plum Tomato Sauce"
    let toppings = ["Clam"]
    
    func cut() {
        print("Cutting pizza into square slices")
    }
}

class ChikagoStylePepperoniPizza: Pizza {
    let name = "Chikago style Deep Dish Pepperoni Pizza"
    let dough = "Extra Thick Crust Dough"
    let sauce = "Plum Tomato Sauce"
    let toppings = ["Pepperoni"]
    
    func cut() {
        print("Cutting pizza into square slices")
    }
}
}

Посетим пиццерию в Нью-Йорке и закажем сырную пиццу

let nyStore = NYPizzaStore()
let NYPizza = nyStore.orderPizza("cheese")
print(NYPizza.name)
/*
Preparing Ny style Sauce and Cheese Pizza
Tossing dough...
Adding sauce
Adding toppings ["Grated Reggiano Cheese"]
Bake for 25 minutes at 350
Cutting the pizza into diagonal slices
Place pizza in the official PizzaStore box
*/
}

Посетим пиццерию в Чикаго и аналогично закажем сырную пиццу

let chikagoStore = ChikagoPizzaStore()
let ChikagoPizza = chikagoStore.orderPizza("cheese")
print(ChikagoPizza.name)
/*
Preparing Chikago style Deep Dish Cheese Pizza
Tossing dough...
Adding sauce
Adding toppings ["Shredded Mozarella Cheese"]
Bake for 25 minutes at 350
Cutting pizza into square slices
Place pizza in the official PizzaStore box
Ny style Sauce and Cheese Pizza
Chikago style Deep Dish Cheese Pizza
*/
}

Реализация паттерна Factory Method: