Начиная серию статей, состоящую из 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, чтобы не пропустить новый материал.