Шаблоны программирования на Swift: Builder
Продолжаем изучать шаблоны программирования на Swift. И сегодня будем разбирать шаблон Builder.
Builder
Вот представьте что у нас есть фабрика. Но в отличии от фабрики из предыдущей статьи, она умеет создавать только телефоны на базе андроида, и еще при этом различной конфигурации. Тоесть, есть один объект, но при этом его состояние может быть совершенно разным, а еще представьте если его очень трудно создавать, и во время создания этого объекта еще и создается миллион дочерних объектов. Именно в такие моменты, нам очень помогает такой паттерн как строитель.
Когда использовать:
1. Создание сложного объекта
2. Процесс создания объекта тоже очень не тривиальный – к примеру получение данных из базы и манипуляция ими.
Сам паттерн состоит из двух компонент – Bulilder и Director. Builder занимается именно построением объекта, а Director знает какой Builder использовать чтобы выдать необходимый продукт.
Пускай у нас есть телефон, который обладает следующими свойствами:
// // proSwift.ru // class AndriodPhone { var osVersion = "" var name = "" var cpuCodeName = "" var RAMsize = 0 var osVersionCode = 0 var launcher = "" func setOSVersion() { self.osVersion = "" } func setName() { self.name = "" } func setCPUCodeName() { self.cpuCodeName = "" } func setRAMsize() { self.RAMsize = 0 } func setOSVersionCode() { self.osVersionCode = 0 } func setLauncher() { self.osVersion = "" } }
Давайте создадим дженерик строителя от которого будут наследоваться конкретные строители:
// // proSwift.ru // class BPAndroidPhoneBuilder { let phone = AndriodPhone() func getPhone() -> AndriodPhone { return self.phone } }
Ну а теперь напишем код для конкретных строителий. К примеру, так бы выглядел строитель для дешевого телефона:
// // proSwift.ru // class LowPricePhoneBuilder: BPAndroidPhoneBuilder { func setOSVersion() { self.phone.osVersion = "Android 2.3" } func setName() { self.phone.name = "Ведрофон" } func setCPUCodeName() { self.phone.cpuCodeName = "Some shitty CPU" } func setRAMsize() { self.phone.RAMsize = 256 } func setOSVersionCode() { self.phone.osVersionCode = 3 } func setLauncher() { self.phone.launcher = "Hia Tsung" } }
И конечно же строительство дорогого телефона:
// // proSwift.ru // class HighPricePhoneBuilder: BPAndroidPhoneBuilder { func setOSVersion() { self.phone.osVersion = "Android 5.0" } func setName() { self.phone.name = "Крутой лопатофон" } func setCPUCodeName() { self.phone.cpuCodeName = "Some shitty, but expensive CPU" } func setRAMsize() { self.phone.RAMsize = 2048 } func setOSVersionCode() { self.phone.osVersionCode = 5 } func setLauncher() { self.phone.launcher = "HTC Sence" } }
Кто-то же должен использовать строителей, потому давайте создадим объект который будет с помощью строителей создавать дешевые или дорогие телефоны:
// // proSwift.ru // class FactorySalesMan { var builder = BPAndroidPhoneBuilder() func setBuilder(aBuilder: BPAndroidPhoneBuilder) { self.builder = aBuilder } func getPhone() -> AndriodPhone { return self.builder.getPhone() } func constuctPhone() { if let builder = builder as? LowPricePhoneBuilder { builder.setOSVersion() builder.setName() builder.setCPUCodeName() builder.setRAMsize() builder.setOSVersionCode() builder.setLauncher() } else if let builder = builder as? HighPricePhoneBuilder { builder.setOSVersion() builder.setName() builder.setCPUCodeName() builder.setRAMsize() builder.setOSVersionCode() builder.setLauncher() } } }
Ну теперь давайте посмотрим что же у нас получилось:
// // proSwift.ru // let cheapPhoneBuilder = LowPricePhoneBuilder() let expensivePhoneBuilder = HighPricePhoneBuilder() let director = FactorySalesMan() director.setBuilder(cheapPhoneBuilder) director.constuctPhone() let phone = director.getPhone() print("Phone name \(phone.name), phone cpu: \(phone.cpuCodeName)") director.setBuilder(expensivePhoneBuilder) director.constuctPhone() let phone2 = director.getPhone() print("Phone2 name \(phone2.name), phone cpu: \(phone2.cpuCodeName)")
И вывод в консоль:
Phone name Ведрофон, phone cpu: Some shitty CPU Phone2 name Крутой лопатофон, phone cpu: Some shitty, but expensive CPU
Пример с GitHub
// // proSwift.ru // // https://github.com/ochococo/Design-Patterns-In-Swift/blob/master/source/creational/builder.swift class DeathStarBuilder { var x: Double? var y: Double? var z: Double? typealias BuilderClosure = (DeathStarBuilder) -> () init(buildClosure: BuilderClosure) { buildClosure(self) } }
Тут в качестве строителя выступает класс, который содержит три свойства и инициализируется функцией, в которую впоследствии передает эти свойства. Тут главное не запутаться. Еще раз. Для создания объекта класса нам нужно передать замыкание, которое будет обрабатывать значение свойств объекта.
Сама Звезда Смерти инициализируется с помощью созданного нами строителя.
// // proSwift.ru // // https://github.com/ochococo/Design-Patterns-In-Swift/blob/master/source/creational/builder.swift struct DeathStar : CustomStringConvertible { let x: Double let y: Double let z: Double init?(builder: DeathStarBuilder) { if let x = builder.x, y = builder.y, z = builder.z { self.x = x self.y = y self.z = z } else { return nil } } var description:String { return "Death Star at (x:\(x) y:\(y) z:\(z))" } }
И теперь используем все что написали для создания объектов.
// // proSwift.ru // // https://github.com/ochococo/Design-Patterns-In-Swift/blob/master/source/creational/builder.swift let empire = DeathStarBuilder { builder in builder.x = 0.1 builder.y = 0.2 builder.z = 0.3 } let deathStar = DeathStar(builder:empire) print(deathStar?.description) // Optional("Death Star at (x:0.1 y:0.2 z:0.3)")
Тут мы создаем строителя, путем передачи той самой функции, о которой говорилось выше. Эта функция присваивает значения координатам, но запросто могла бы вычислить сложные математические уравнения расчета движения космической станции в подпространстве. А после получаем Звезду Смерти, используя строителя.
не очень удачный пример…
в constuctPhone() идем switchCase анализ, нарушается OpenCl.P.