Порождающие паттерны проектирования. Примеры в iOS

Начиная серию статей, состоящую из 3‑х частей (порождающие паттерны проектирования, структурные и поведенческие), скажу честно, я видел всего лишь одно-два собеседования, где спрашивали определения каких-либо паттернов проектирования (в небольшом опросе канала iOS-interview аналогичная ситуация, только 5% из ответивших спрашивали конкретные паттерны на собеседовании).

На собеседованиях про паттерны спрашивают часто, но вопросы обычно звучат так: «Работали ли вы с какими-либо паттернами проектирования?», «Перечислите паттерны проектирования, которые вы знаете». Вы перечисляете список из 3-5 паттернов и на этом разговор на данную тему заканчивается. Но знания подходов, описывающих тот или инной паттерн полезны в повседневной работе, а рассказав про них на собеседовании вы автоматически прибавите несколько баллов к своей экспертизе.

Всего существует 23 паттерна, все они очень подробно описаны в книге банды четырех «Паттерны объектно-ориентированного проектирования«. Все паттерны делятся на 3 группы: порождающие, структурные и поведенческие. В данной статье мы разберем Порождающие паттерны/шаблоны проектирования.

1. Abstract Factory — Абстрактная фабрика

Определение: Абстрактная фабрика позволяет создать семейство связанных объектов, не привязываясь к конкретным классам создаваемых объектов.

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

Реализация: Вам необходимо написать интерфейс, в котором будут определены методы создания телефона, ноутбука и планшета. Далее под каждого производителя нужно создать собственный класс, а также реализовать методы интерфейса.

// Интерфейс Абстрактной Фабрики объявляет набор методов, которые возвращают
// различные абстрактные продукты. Эти продукты называются семейством.
protocol AbstractFactory {

    func createProductA() -> AbstractProductA
    func createProductB() -> AbstractProductB
}

// Каждый отдельный продукт семейства продуктов должен иметь базовый интерфейс.
protocol AbstractProductA {

// Методы продукта А, которые могут быть реализованы во всех продуктах семейства
}

// Продукты фабрики AbstractProductA
class iPhoneA1: AbstractProductA {

}

class iPhoneA2: AbstractProductA {

}

// Базовый интерфейс AbstractProductB
protocol AbstractProductB {

// Методы продукта А, которые могут быть реализованы во всех продуктах семейства
}

// Продукты фабрики AbstractProductB
class TabletB1: AbstractProductB {

}

class TabletB2: AbstractProductB {

}

// Конкретная Фабрика производит семейство продуктов одной вариации.
class Factory1: AbstractFactory {

    func createProductA() -> AbstractProductA {
        return iPhoneA1()
    }

    func createProductB() -> AbstractProductB {
        return TabletB1()
    }
}

// Конкретная Фабрика производит семейство продуктов другой вариации.
class Factory2: AbstractFactory {

    func createProductA() -> AbstractProductA {
        return iPhoneA2()
    }

    func createProductB() -> AbstractProductB {
        return TabletB2()
    }
}

// Клиентский код работает с фабриками и продуктами только через абстрактные типы
class Client {

    static func createProducts(for factory: AbstractFactory) {
        let productA = factory.createProductA()
        let productB = factory.createProductB()
    }
}

// Теперь посмотрим как это работает
class AbstractFactoryImplementation {

    func productCreation() {
        Client.createProducts(for: Factory1())
        Client.createProducts(for: Factory2())
    }
}

2. Builder — Строитель

Определение: Паттерн Строитель позволяет создавать объект пошагово. Паттерн состоит из двух компонентов: Bulilder и Director. Builder занимается построением объекта, а Director знает какой Builder использовать, чтобы создать необходимый продукт.

Пример: Вы создаете какой-то объект, например — дом. Создание дома происходит в несколько этапов. Паттерн Строитель позволяет указать последовательность в которой будет происходить создание дома.

Реализация: Посмотрим на реализацию паттерна Строитель на примере создания дома, состоящего из стен и крыши.

import Foundation

// Модели
struct Walls {
    var width: Float
    var height: Float
}

struct Roof {
    var width: Float
    var height: Float
}

struct House {
    var walls: Walls?
    var roof: Roof?
}

// Builder
class HouseBuilder {
  
    private var house: House?

    func setValue(walls: Walls?, roof: Roof?) {
        house?.walls = walls
        house?.roof = roof
    }
    
    func getResult() -> House? {
        return house
    }
}

// Использование
let walls = Walls(width: 1.5, height: 2.30)
let roof = Roof(width: 2.5, height: 3.30)

let builder = HouseBuilder()
builder.setValue(walls: walls, roof: roof)

let house = builder.getResult()

3. Factory Method — Фабричный метод

Определение: Фабричный метод позволяет создать различные продукты без указания конкретных классов продуктов.

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

Реализация: Протокол определяет стандартное поведение и делегирует детали создания субклассам, определенным пользователем.

// Протокол Создатель объявляет фабричный метод, который должен возвращать объект класса Продукт.
protocol Creator {

    // Базовая реализация создателя
    func someOperation() -> String
    
    func factoryMethod() -> Product
}

// Это расширение реализует базовое поведение Создателя.
extension Creator {

    func someOperation() -> String {
        // Получаем продукт.
        let product = factoryMethod()

        // Работаем с этим продуктом.
        return "Базовое поведение или описание " + product.operation()
    }
}

// Создатели переопределяют фабричный метод для того, чтобы изменить тип результирующего продукта.
class Creator1: Creator {

    public func factoryMethod() -> Product {
        return Product1()
    }
}

// Создадим второго создателя.
class Creator2: Creator {

    public func factoryMethod() -> Product {
        return Product2()
    }
}

// Протокол Продукта объявляет операции, которые должны выполнять все создаваемые продукты.
protocol Product {

    func operation() -> String
}

// Создадим конкретные продукты.
class Product1: Product {

    func operation() -> String {
        return "Описание продукта"
    }
}

class Product2: Product {

    func operation() -> String {
        return "Описание продукта"
    }
}


// Клиентский код работает с экземпляром конкретного создателя
class Client {

    static func createProducts(creator: Creator) {
        
    }
}

// Посмотрим это работает.
class FactoryMethodConceptual {

    func productCreation() {
        Client.createProducts(creator: Creator1())
        Client.createProducts(creator: Creator2())
    }
}

4. Prototype — Прототип

Определение: Один из самых простых паттернов проектирования — Prototype (Прототип). Паттерн представляет собой технику клонирования объектов, то есть позволяет создавать объект на основе ранее созданных объектов.

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

Реализация: Реализаций данного паттерна может быть много. Но важно понимать разницу между глубоким и поверхностным копированием. Разницу с примерами уже писал в этой статье: В чем разница между копированием массива и структуры?

Давайте посмотрим на пример реализации данного паттерна:

// Создаим класс с реализацией протокола NSCopying
class Contact: NSObject, NSCopying {
    
    var firstName: String
    var lastName: String
    
    init(firstName: String, lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
    }
    
    func copy(with zone: NSZone? = nil) -> Any {
        return Contact(firstName: self.firstName, lastName: self.lastName)
    }
}

// Создадим первый объект
let firstContact = Contact(firstName: "Tom", lastName: "Leader")

// Используем глубокое копирование и изменяем один из параметров
let secondContact = firstContact.copy() as! Contact
secondContact.lastName = "Poul"

// Смотрим результат.
print(firstContact.firstName, firstContact.lastName) // Tom Leader
print(secondContact.firstName, secondContact.lastName) // Tom Poul

Вы можете отказаться от реализации протокола NSCopying, но тогда получите поверхностное копирование. Рекомендую попробовать это сделать в Playground!

5. Singleton — Одиночка

Определение: Считается, что Синглтон — один из самых популярных паттернов проектирования. Но также считается, что он является и антипаттерном.

Паттерн Синглтон гарантирует, что создается только один экземпляр класса.

Пример: Примеров может быть большое множество. Например, вам необходимо реализовать нетворк менеджер, который будет доступен из любой точки приложения.

Реализация:

// Реализация
class NetworkManager {
    
    // MARK: - Properties
    static let shared = NetworkManager(baseURL: URL(string: "https://ios-interview.ru")!)
    
    // MARK: -
    let baseURL: URL
    
    // Initialization
    private init(baseURL: URL) {
        self.baseURL = baseURL
    }
}

// Как использовать
print(NetworkManager.shared.baseURL) // https://ios-interview.ru

Куда пойти дальше?

Выразить благодарность или найти уникальный материал вы можете в boosty.

Подписывайтесь на мой Telegram-канал iOS Interview Channel, чтобы не пропустить новый материал.


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

Ваш адрес email не будет опубликован. Обязательные поля помечены *