
Всем привет! Зачастую чтобы в чем то разобраться полезнее один раз увидеть конкретный пример чем несколько раз прочитать заумное описание.Решил написать ряд небольших статей для начинающих, в которых дать краткое описание основных паттернов проектирования и привести лаконичные примеры их использования.Данная статья, как можно догадаться из названия =), посвящена порождающим паттернам.
Фабричный метод /Factory Method
Фабричный метод — это порождающий паттерн проектирования, который определяет общий интерфейс для создания объектов в суперклассе, позволяя подклассам изменять тип создаваемых объектов.
Представим, что мы разрабатываем UI фреймворк и хотим, чтобы он мог использоваться и в iOS и в Windows. У каждой из платформ свои уникальные UI элементы. Нам нужен механизм, который создавал бы элементы в зависимости от платформы на которой мы запустили нашу программу. В этом нам может помочь паттерн фабричный метод.
Разберем на примере кнопки.
Для начала создадим протокол.
protocol IButton { func render() func onClick() }
Создадим кнопки для windows и для iOS.
class windowsButton: IButton { func render() { print("wiwindowsButton.windowsButton") } func onClick() { print("windowsButton.onClick") } } class iosButton: IButton { func render() { print("iosButton.windowsButton") } func onClick() { print("iosButton.onClick") } }
Определим протокол класса создателя. Это тот класс, который будет создавать кнопку и отображать ее на экране.
protocol iCreator { func render() func createButton() -> IButton }
Определим классы создатели для обеих платформ.
class windowsCreator: iCreator { func render() { let button = createButton() button.render() print("rendering windows button") } func createButton() -> IButton { print("creating windows button") return windowsButton() } } class iosCreator: iCreator { func render() { let button = createButton() button.render() print("rendering ios button") } func createButton() -> IButton { print("creating ios button") return iosButton() } }
Обратите внимание, что в метода createButton iosCreator возвращает iosButton, а windowsCreator соответственно windowsButton.
Теперь наш код может в зависимости от системы, в которой он запущен генерировать iOS или Windows UI элемент.
// Application enum System { case windows case ios } class MyAplication { let creator: iCreator? init(system: System) { switch system { case .windows: creator = windowsCreator() case.ios: creator = iosCreator() } } }
В момент запуска системы определяется какой тип класса создателя будет использоваться.
let myApplication = MyAplication(system: .windows) myApplication.creator?.render()
В данном случае создастся и отобразиться кнопка для Windows.
Абстрактная фабрика / Abstract Factory
Абстрактная фабрика — это порождающий паттерн проектирования, который позволяет создавать семейства связанных объектов, не привязываясь к конкретным классам создаваемых объектов.
Логическим продолжением фабричного метода является паттерн абстрактная фабрика.
Что если нам нужно создавать не один UI элемент, а целое семейство элементов в зависимости от среды в которой запущена наша программа или иных условий.
Воспользуемся паттерном абстрактная фабрика.
Создадим протоколы элементов, которые мы хотим создавать. Для простоты возьмем два. Кнопку и чекбокс.
protocol IButton { func pressButton() } protocol ICheckmark { func chooseCheckMark() }
Создадим классы кнопок.
class WindowsButton: IButton { func pressButton() { print("Windows button pressed") } } class IosButton: IButton { func pressButton() { print("IOS button pressed") } }
И классы чекбоксов.
class WindowsCheckmark: ICheckmark { func chooseCheckMark() { print("Windows checkmark choosen") } } class IosCheckmark: ICheckmark { func chooseCheckMark() { print("IOS checkmark choosen") } }
Определим протокол фабрики, которая будет поставлять нам элементы.
protocol AbstractUIElementsFactory { func makeButton() -> IButton func makeCheckmark() -> ICheckmark }
Определим Windows фабрику.
class WindowsUIElementsFactory: AbstractUIElementsFactory { func makeButton() -> IButton { print("Windows button is creating ...") return WindowsButton() } func makeCheckmark() -> ICheckmark { print("Windows checkmark is creating ...") return WindowsCheckmark() } }
И iOS фабрику.
class IosUIElementsFactory: AbstractUIElementsFactory { func makeButton() -> IButton { print("IOS button is creating ...") return IosButton() } func makeCheckmark() -> ICheckmark { print("IOS checkmark is creating ...") return IosCheckmark() } }
В клиентском коде мы создаем нужные элементы, вызывая методы фабрики.
class Client { static func createUIElements(factory: AbstractUIElementsFactory) { let button = factory.makeButton() let checkmark = factory.makeCheckmark() button.pressButton() checkmark.chooseCheckMark() } }
А тип элементов, которые мы получаем зависит от типа переданной фабрики.
Client.createUIElements(factory: WindowsUIElementsFactory()) print("") Client.createUIElements(factory: IosUIElementsFactory())
Строитель / Builder
Строитель — это порождающий паттерн проектирования, который позволяет создавать сложные объекты пошагово. Строитель даёт возможность использовать один и тот же код строительства для получения разных представлений объектов.
Как следует из определения строитель даёт возможность использовать один и тот же код строительства для получения разных представлений объектов.
Представьте что у нас есть сложный класс с множеством настроек или несколько взаимосвязанных классов, зависящий друг от друга. Настраивая их каждый раз в ручную легко ошибиться. Тут нам на помощь может прийти паттерн строитель.
Предположим у нас есть класс автомобиль.
final class Car { var seats = 0 var engine = "" var tripComputer = "" var gps = false init() { print("Car is creating ...") } func printDescription() { print("seats: \(seats)\nengine: \(engine)\ntripComputer: \(tripComputer)\ngps: \(gps)\n") } }
Так как автомобиль технически сложное изделие, нам нужна инструкция к нему, описывающая все характеристики и свойства автомобиля.
final class Manual { var seats = "" var engine = "" var tripComputer = "" var gps = "" init() { print("Manual is creating ...") } func printDescription() { print("seats: \(seats)\nengine: \(engine)\ntripComputer: \(tripComputer)\ngps: \(gps)\n") } }
Для настройки классов автомобиль и инструкция мы можем воспользоваться дополнительным классом строителя, который знает как их настроить, не пропустив какие то необходимые важные действия.
Определим протокол строителя.
protocol Builder { func reset() func setSeats(_ : Int) func setEngine(_ : String) func setTripComputer(_ : String) func setGPS(_ : Bool) }
Определим конкретный класс строителя автомобиля.
final class CarBuilder: Builder { private var car = Car() // MARK: - Protocol methods func reset() { car = Car() } func setSeats(_ seats: Int) { car.seats = seats } func setEngine(_ engine: String) { car.engine = engine } func setTripComputer(_ computer: String) { car.tripComputer = computer } func setGPS(_ isSet: Bool) { car.gps = isSet ? true : false } // MARK: - getResult method func getResult() -> Car { return car } }
А так же класс строителя инструкции.
final class ManualBuilder: Builder { private var manual = Manual() // MARK: - Protocol methods func reset() { manual = Manual() } func setSeats(_ seats: Int) { let ending = seats == 1 ? "seat" : "seats" manual.seats = "Car has \(seats) \(ending)" } func setEngine(_ engine: String) { manual.engine = "Car has \(engine) engine" } func setTripComputer(_ computer: String) { manual.tripComputer = "Car has \(computer) computer" } func setGPS(_ isSet: Bool) { manual.gps = isSet ? "GPS is set" : "There is no GPS" } // MARK: - getResult method func getResult() -> Manual { return manual } }
Так же мы можем создать не обязательный класс директора и предать ему управление строителями.
final class Director { func constructSportsCar(builder: Builder) { builder.reset() builder.setSeats(2) builder.setEngine("Honda") builder.setTripComputer("Apple") builder.setGPS(true) } }
Директор вызывает все необходимые методы и задает необходимые значения свойств. Класс директор в паттерне строитель не обязателен, мы можем напрямую вызывать методы строителя в нашем коде. Но директор структурирует вызовы строители и тем самым может быть нам полезен.
Далее мы можем создать автомобиль и инструкцию к нему воспользовавшись соответствующими строителями.
// Создаем директора и строителей let director = Director() let carBuilder = CarBuilder() let manualBuilder = ManualBuilder() // Передаем в метод директора стрителя авто director.constructSportsCar(builder: carBuilder) let car = carBuilder.getResult() car.printDescription() // Передаем в метод директора стрителя инструкции director.constructSportsCar(builder: manualBuilder) let manual = manualBuilder.getResult() manual.printDescription()
Важно, что результат строительства мы получаем не от директора, а от строителя так как директор чаще всего не знает и не зависит от конкретных классов строителей и продуктов.
let car = carBuilder.getResult()
Прототип / Prototype
Прототип — это порождающий паттерн проектирования, который позволяет копировать объекты, не вдаваясь в подробности их реализации.
Представим, что у нас есть объект, который нужно скопировать. Проблема в том, что у объекта могут быть приватные поля, доступа к которым из вызывающего кода у нас нет.
Как быть в таком случае? Давайте передадим обязанность копировать себя самому объекту, ведь он про себя знает все и имеет доступ ко всем своим свойствам, включая приватные.
В Swift у нас есть встроенная поддержка копирования. Чтобы сделать класс, который может копировать сам себя, нам нужно реализовать в нём протокол NSCopying, а именно методcopy.
Продолжим автомобильную тему. Создадим класс автомобиль.
class Car: NSCopying, Equatable { var model: String var color: String var numberOfSeats: Int required init(model: String = "", color: String = "", numberOfSeats: Int = 2) { self.model = model self.color = color self.numberOfSeats = numberOfSeats } // Реализуем возможность копирования // MARK: - NSCopying func copy(with zone: NSZone? = nil) -> Any { let protorype = type(of: self).init() protorype.model = model protorype.color = color protorype.numberOfSeats = numberOfSeats return protorype } // Дополнительно реализуем возможность сравнения, чтобы иметь возможность сравнить скопированный авто с оригиналом. // MARK: - Equatable static func == (lhs: Car, rhs: Car) -> Bool { return lhs.model == rhs.model && lhs.color == rhs.color && lhs.numberOfSeats == rhs.numberOfSeats } }
В вызывающем коде создаем машину, клонившем ее и сравниваем два экземпляра.
let teslaCar = Car(model: "Tesla", color: "Red", numberOfSeats: 2) teslaCar.engine = "TeslaElectric" let teslaCarCopy = teslaCar.copy() as? Car print(teslaCar == teslaCar)
Одиночка / Singleton
Иногда нам нужно, чтобы в приложении был только один экземпляр како-то класса. Это может быть, например, хранилище данных или API менеджер.
Мы можем реализовать данное поведение с помощью паттерну одиночка.
class Singleton { // Статическое поле, управляющие доступом к экземпляру одиночки. // Эта реализация позволяет сохранять только один экземпляр класса. static var shared: Singleton = { let instance = Singleton() // ... настройка объекта // ... return instance }() // Инициализатор Одиночки всегда должен быть скрытым, чтобы предотвратить // прямое создание объекта через инициализатор. private init() {} // Любой одиночка должен содержать некоторую бизнес-логику, // которая может быть выполнена на его экземпляре. func someBusinessLogic() -> String { // ... return "Result of the 'someBusinessLogic' call" } }
Одиночка не должен быть копируемым. Для этого реализуем метод copy(with zone:) так, чтобы одиночка возвращал самого себя.
extension Singleton: NSCopying { func copy(with zone: NSZone? = nil) -> Any { return self } }
В клиентском коде мы можем убедиться, что экземпляр класса одиночки у на один.
let instance1 = Singleton.shared let instance2 = Singleton.shared if (instance1 === instance2) { print("Singleton works, both variables contain the same instance.") } else { print("Singleton failed, variables contain different instances.") }
На этом про порождающие паттерны все. Надеюсь, что данная статья поможет начинающим разработчикам разобраться в такой не самой простой теме как паттерну проектирования
ссылка на оригинал статьи https://habr.com/ru/articles/916564/
Добавить комментарий