Swift Language
Шаблоны проектирования - создание
Поиск…
Вступление
Шаблоны проектирования - это общие решения проблем, которые часто возникают при разработке программного обеспечения. Ниже приведены шаблоны стандартизованных передовых методов структурирования и проектирования кода, а также примеры общих контекстов, в которых эти шаблоны проектирования были бы подходящими.
Созданные шаблоны проектирования абстрагируют создание объектов, чтобы сделать систему более независимой от процесса создания, составления и представления.
одиночка
Синглтоны - это часто используемый шаблон проектирования, который состоит из одного экземпляра класса, который используется во всей программе.
В следующем примере мы создаем static
свойство, которое содержит экземпляр класса Foo
. Помните, что static
свойство разделяется между всеми объектами класса и не может быть перезаписано подклассом.
public class Foo
{
static let shared = Foo()
// Used for preventing the class from being instantiated directly
private init() {}
func doSomething()
{
print("Do something")
}
}
Использование:
Foo.shared.doSomething()
Обязательно запомните private
инициализатор:
Это гарантирует, что ваши синглтоны действительно уникальны и не позволяют внешним объектам создавать собственные экземпляры вашего класса благодаря контролю доступа. Поскольку все объекты поставляются с открытым инициализатором по умолчанию в Swift, вам необходимо переопределить ваш init и сделать его приватным. KrakenDev
Заводской метод
В программировании на основе классов шаблон фабричного метода представляет собой шаблон создания, который использует фабричные методы для решения проблемы создания объектов без указания точного класса объекта, который будет создан. Ссылка на Wikipedia
protocol SenderProtocol
{
func send(package: AnyObject)
}
class Fedex: SenderProtocol
{
func send(package: AnyObject)
{
print("Fedex deliver")
}
}
class RegularPriorityMail: SenderProtocol
{
func send(package: AnyObject)
{
print("Regular Priority Mail deliver")
}
}
// This is our Factory
class DeliverFactory
{
// It will be responsable for returning the proper instance that will handle the task
static func makeSender(isLate isLate: Bool) -> SenderProtocol
{
return isLate ? Fedex() : RegularPriorityMail()
}
}
// Usage:
let package = ["Item 1", "Item 2"]
// Fedex class will handle the delivery
DeliverFactory.makeSender(isLate:true).send(package)
// Regular Priority Mail class will handle the delivery
DeliverFactory.makeSender(isLate:false).send(package)
Делая это, мы не зависим от реальной реализации класса, делая sender()
полностью прозрачным для тех, кто его потребляет.
В этом случае все, что нам нужно знать, это то, что отправитель будет обрабатывать доставку и выдает метод send()
. Есть еще несколько преимуществ: уменьшить сцепление классов, легче протестировать, проще добавить новое поведение, не изменяя, кто его потребляет.
В рамках объектно-ориентированного проектирования интерфейсы обеспечивают уровни абстракции, которые облегчают концептуальное объяснение кода и создают барьер, предотвращающий зависимость. Ссылка на Wikipedia
наблюдатель
Шаблон наблюдателя - это объект, называемый субъектом, который ведет список своих иждивенцев, называемых наблюдателями, и автоматически уведомляет их о любых изменениях состояния, обычно, вызывая один из своих методов. Он в основном используется для реализации распределенных систем обработки событий. Шаблон Observer также является ключевой частью знакомого архитектурного шаблона model-view-controller (MVC). Ссылка на Wikipedia
В принципе шаблон наблюдателя используется, когда у вас есть объект, который может уведомить наблюдателей о некоторых изменениях поведения или состояния.
Сначала давайте создадим глобальную ссылку (вне класса) для Центра уведомлений:
let notifCentre = NotificationCenter.default
Отлично, теперь мы можем назвать это где угодно. Затем мы хотели бы зарегистрировать класс в качестве наблюдателя ...
notifCentre.addObserver(self, selector: #selector(self.myFunc), name: "myNotification", object: nil)
Это добавляет класс в качестве наблюдателя для «readForMyFunc». Он также указывает, что функция myFunc должна вызываться при получении этого уведомления. Эта функция должна быть написана в том же классе:
func myFunc(){
print("The notification has been received")
}
Одно из преимуществ этого шаблона состоит в том, что вы можете добавлять в качестве наблюдателей множество классов и, таким образом, выполнять много действий после одного уведомления.
Теперь уведомление может быть просто отправлено (или отправлено, если хотите) почти из любого места в коде с линией:
notifCentre.post(name: "myNotification", object: nil)
Вы также можете передавать информацию с уведомлением в виде словаря
let myInfo = "pass this on"
notifCentre.post(name: "myNotification", object: ["moreInfo":myInfo])
Но тогда вам нужно добавить уведомление о своей функции:
func myFunc(_ notification: Notification){
let userInfo = (notification as NSNotification).userInfo as! [String: AnyObject]
let passedInfo = userInfo["moreInfo"]
print("The notification \(moreInfo) has been received")
//prints - The notification pass this on has been received
}
Цепочка ответственности
В объектно-ориентированном дизайне шаблон цепочки ответственности представляет собой шаблон проектирования, состоящий из источника
command
объектов и серии объектовprocessing
. Каждый объектprocessing
содержит логику, которая определяет типы объектов команд, которые он может обрабатывать; остальные передаются на следующий объектprocessing
в цепочке. Механизм также существует для добавления новых объектовprocessing
в конец этой цепочки. Википедия
Создание классов, составляющих цепочку ответственности.
Сначала мы создаем интерфейс для всех объектов processing
.
protocol PurchasePower {
var allowable : Float { get }
var role : String { get }
var successor : PurchasePower? { get set }
}
extension PurchasePower {
func process(request : PurchaseRequest){
if request.amount < self.allowable {
print(self.role + " will approve $ \(request.amount) for \(request.purpose)")
} else if successor != nil {
successor?.process(request: request)
}
}
}
Затем мы создаем объект command
.
struct PurchaseRequest {
var amount : Float
var purpose : String
}
Наконец, создавая объекты, составляющие цепочку ответственности.
class ManagerPower : PurchasePower {
var allowable: Float = 20
var role : String = "Manager"
var successor: PurchasePower?
}
class DirectorPower : PurchasePower {
var allowable: Float = 100
var role = "Director"
var successor: PurchasePower?
}
class PresidentPower : PurchasePower {
var allowable: Float = 5000
var role = "President"
var successor: PurchasePower?
}
Инициируйте и соедините его вместе:
let manager = ManagerPower()
let director = DirectorPower()
let president = PresidentPower()
manager.successor = director
director.successor = president
Механизм объединения объектов здесь - это доступ к свойствам
Создание запроса для его запуска:
manager.process(request: PurchaseRequest(amount: 2, purpose: "buying a pen")) // Manager will approve $ 2.0 for buying a pen
manager.process(request: PurchaseRequest(amount: 90, purpose: "buying a printer")) // Director will approve $ 90.0 for buying a printer
manager.process(request: PurchaseRequest(amount: 2000, purpose: "invest in stock")) // President will approve $ 2000.0 for invest in stock
Итератор
В компьютерном программировании итератор является объектом, который позволяет программисту перемещаться по контейнеру, в частности спискам. Википедия
struct Turtle {
let name: String
}
struct Turtles {
let turtles: [Turtle]
}
struct TurtlesIterator: IteratorProtocol {
private var current = 0
private let turtles: [Turtle]
init(turtles: [Turtle]) {
self.turtles = turtles
}
mutating func next() -> Turtle? {
defer { current += 1 }
return turtles.count > current ? turtles[current] : nil
}
}
extension Turtles: Sequence {
func makeIterator() -> TurtlesIterator {
return TurtlesIterator(turtles: turtles)
}
}
И пример использования
let ninjaTurtles = Turtles(turtles: [Turtle(name: "Leo"),
Turtle(name: "Mickey"),
Turtle(name: "Raph"),
Turtle(name: "Doney")])
print("Splinter and")
for turtle in ninjaTurtles {
print("The great: \(turtle)")
}
Шаблон Builder
Шаблон построителя представляет собой шаблон разработки программного обеспечения для создания объектов . В отличие от абстрактного заводского шаблона и шаблона фабричного метода, целью которого является включение полиморфизма, намерение шаблона-строителя состоит в том, чтобы найти решение для анти-шаблона конструктора телескопа. Конструкция антивибратора конструктора телескопов возникает, когда увеличение комбинации параметров конструктора объекта приводит к экспоненциальному списку конструкторов. Вместо использования множества конструкторов шаблон построителя использует другой объект, строитель, который по шагам получает каждый параметр инициализации и затем возвращает результирующий построенный объект одновременно.
Основная цель шаблона построителя - установить конфигурацию по умолчанию для объекта из его создания. Это посредник между объектом будет построен и все другие объекты, связанные с его построением.
Пример:
Чтобы это стало понятным, давайте посмотрим на пример Car Builder .
Подумайте, что у нас есть класс Car, содержащий множество вариантов для создания объекта, например:
- Цвет.
- Количество мест.
- Количество колес.
- Тип.
- Тип передачи.
- Мотор.
- Доступность подушки безопасности.
import UIKit
enum CarType {
case
sportage,
saloon
}
enum GearType {
case
manual,
automatic
}
struct Motor {
var id: String
var name: String
var model: String
var numberOfCylinders: UInt8
}
class Car: CustomStringConvertible {
var color: UIColor
var numberOfSeats: UInt8
var numberOfWheels: UInt8
var type: CarType
var gearType: GearType
var motor: Motor
var shouldHasAirbags: Bool
var description: String {
return "color: \(color)\nNumber of seats: \(numberOfSeats)\nNumber of Wheels: \(numberOfWheels)\n Type: \(gearType)\nMotor: \(motor)\nAirbag Availability: \(shouldHasAirbags)"
}
init(color: UIColor, numberOfSeats: UInt8, numberOfWheels: UInt8, type: CarType, gearType: GearType, motor: Motor, shouldHasAirbags: Bool) {
self.color = color
self.numberOfSeats = numberOfSeats
self.numberOfWheels = numberOfWheels
self.type = type
self.gearType = gearType
self.motor = motor
self.shouldHasAirbags = shouldHasAirbags
}
}
Создание объекта автомобиля:
let aCar = Car(color: UIColor.black,
numberOfSeats: 4,
numberOfWheels: 4,
type: .saloon,
gearType: .automatic,
motor: Motor(id: "101", name: "Super Motor",
model: "c4", numberOfCylinders: 6),
shouldHasAirbags: true)
print(aCar)
/* Printing
color: UIExtendedGrayColorSpace 0 1
Number of seats: 4
Number of Wheels: 4
Type: automatic
Motor: Motor(id: "101", name: "Super Motor", model: "c4", numberOfCylinders: 6)
Airbag Availability: true
*/
Проблема возникает, когда создание объекта автомобиля состоит в том, что автомобиль требует создания многих конфигурационных данных.
Для применения шаблона Builder параметры инициализатора должны иметь значения по умолчанию, которые при необходимости изменяются .
Класс CarBuilder:
class CarBuilder {
var color: UIColor = UIColor.black
var numberOfSeats: UInt8 = 5
var numberOfWheels: UInt8 = 4
var type: CarType = .saloon
var gearType: GearType = .automatic
var motor: Motor = Motor(id: "111", name: "Default Motor",
model: "T9", numberOfCylinders: 4)
var shouldHasAirbags: Bool = false
func buildCar() -> Car {
return Car(color: color, numberOfSeats: numberOfSeats, numberOfWheels: numberOfWheels, type: type, gearType: gearType, motor: motor, shouldHasAirbags: shouldHasAirbags)
}
}
Класс CarBuilder
определяет свойства, которые можно изменить для редактирования значений созданного объекта автомобиля.
Давайте построим новые автомобили с помощью CarBuilder
:
var builder = CarBuilder()
// currently, the builder creates cars with default configuration.
let defaultCar = builder.buildCar()
//print(defaultCar.description)
/* prints
color: UIExtendedGrayColorSpace 0 1
Number of seats: 5
Number of Wheels: 4
Type: automatic
Motor: Motor(id: "111", name: "Default Motor", model: "T9", numberOfCylinders: 4)
Airbag Availability: false
*/
builder.shouldHasAirbags = true
// now, the builder creates cars with default configuration,
// but with a small edit on making the airbags available
let safeCar = builder.buildCar()
print(safeCar.description)
/* prints
color: UIExtendedGrayColorSpace 0 1
Number of seats: 5
Number of Wheels: 4
Type: automatic
Motor: Motor(id: "111", name: "Default Motor", model: "T9", numberOfCylinders: 4)
Airbag Availability: true
*/
builder.color = UIColor.purple
// now, the builder creates cars with default configuration
// with some extra features: the airbags are available and the color is purple
let femaleCar = builder.buildCar()
print(femaleCar)
/* prints
color: UIExtendedSRGBColorSpace 0.5 0 0.5 1
Number of seats: 5
Number of Wheels: 4
Type: automatic
Motor: Motor(id: "111", name: "Default Motor", model: "T9", numberOfCylinders: 4)
Airbag Availability: true
*/
Преимущество применения шаблона Builder заключается в простоте создания объектов, которые должны содержать большую часть конфигураций, устанавливая значения по умолчанию, а также легкость изменения этих значений по умолчанию.
Возьмите дальше:
В качестве хорошей практики все свойства, требующие значений по умолчанию, должны быть в отдельном протоколе , который должен быть реализован самим классом и его создателем.
Вернемся к нашему примеру, давайте создадим новый протокол под названием CarBluePrint
:
import UIKit
enum CarType {
case
sportage,
saloon
}
enum GearType {
case
manual,
automatic
}
struct Motor {
var id: String
var name: String
var model: String
var numberOfCylinders: UInt8
}
protocol CarBluePrint {
var color: UIColor { get set }
var numberOfSeats: UInt8 { get set }
var numberOfWheels: UInt8 { get set }
var type: CarType { get set }
var gearType: GearType { get set }
var motor: Motor { get set }
var shouldHasAirbags: Bool { get set }
}
class Car: CustomStringConvertible, CarBluePrint {
var color: UIColor
var numberOfSeats: UInt8
var numberOfWheels: UInt8
var type: CarType
var gearType: GearType
var motor: Motor
var shouldHasAirbags: Bool
var description: String {
return "color: \(color)\nNumber of seats: \(numberOfSeats)\nNumber of Wheels: \(numberOfWheels)\n Type: \(gearType)\nMotor: \(motor)\nAirbag Availability: \(shouldHasAirbags)"
}
init(color: UIColor, numberOfSeats: UInt8, numberOfWheels: UInt8, type: CarType, gearType: GearType, motor: Motor, shouldHasAirbags: Bool) {
self.color = color
self.numberOfSeats = numberOfSeats
self.numberOfWheels = numberOfWheels
self.type = type
self.gearType = gearType
self.motor = motor
self.shouldHasAirbags = shouldHasAirbags
}
}
class CarBuilder: CarBluePrint {
var color: UIColor = UIColor.black
var numberOfSeats: UInt8 = 5
var numberOfWheels: UInt8 = 4
var type: CarType = .saloon
var gearType: GearType = .automatic
var motor: Motor = Motor(id: "111", name: "Default Motor",
model: "T9", numberOfCylinders: 4)
var shouldHasAirbags: Bool = false
func buildCar() -> Car {
return Car(color: color, numberOfSeats: numberOfSeats, numberOfWheels: numberOfWheels, type: type, gearType: gearType, motor: motor, shouldHasAirbags: shouldHasAirbags)
}
}
Преимущество объявления свойств, требующих значения по умолчанию в протоколе, - это принудительное внедрение любого нового добавленного свойства; Когда класс соответствует протоколу, он должен объявлять все свои свойства / методы.
Учтите, что требуется новая функция, которая должна быть добавлена к проекту создания автомобиля под названием «название батареи»:
protocol CarBluePrint {
var color: UIColor { get set }
var numberOfSeats: UInt8 { get set }
var numberOfWheels: UInt8 { get set }
var type: CarType { get set }
var gearType: GearType { get set }
var motor: Motor { get set }
var shouldHasAirbags: Bool { get set }
// adding the new property
var batteryName: String { get set }
}
После добавления нового свойства обратите внимание на то, что возникнут две ошибки времени компиляции, сообщив, что в соответствии с протоколом CarBluePrint
требуется объявить свойство «batteryName». Это гарантирует, что CarBuilder
объявит и установит значение по умолчанию для свойства batteryName
.
После добавления batteryName
new в протокол CarBluePrint
реализация классов Car
и CarBuilder
должна быть:
class Car: CustomStringConvertible, CarBluePrint {
var color: UIColor
var numberOfSeats: UInt8
var numberOfWheels: UInt8
var type: CarType
var gearType: GearType
var motor: Motor
var shouldHasAirbags: Bool
var batteryName: String
var description: String {
return "color: \(color)\nNumber of seats: \(numberOfSeats)\nNumber of Wheels: \(numberOfWheels)\nType: \(gearType)\nMotor: \(motor)\nAirbag Availability: \(shouldHasAirbags)\nBattery Name: \(batteryName)"
}
init(color: UIColor, numberOfSeats: UInt8, numberOfWheels: UInt8, type: CarType, gearType: GearType, motor: Motor, shouldHasAirbags: Bool, batteryName: String) {
self.color = color
self.numberOfSeats = numberOfSeats
self.numberOfWheels = numberOfWheels
self.type = type
self.gearType = gearType
self.motor = motor
self.shouldHasAirbags = shouldHasAirbags
self.batteryName = batteryName
}
}
class CarBuilder: CarBluePrint {
var color: UIColor = UIColor.red
var numberOfSeats: UInt8 = 5
var numberOfWheels: UInt8 = 4
var type: CarType = .saloon
var gearType: GearType = .automatic
var motor: Motor = Motor(id: "111", name: "Default Motor",
model: "T9", numberOfCylinders: 4)
var shouldHasAirbags: Bool = false
var batteryName: String = "Default Battery Name"
func buildCar() -> Car {
return Car(color: color, numberOfSeats: numberOfSeats, numberOfWheels: numberOfWheels, type: type, gearType: gearType, motor: motor, shouldHasAirbags: shouldHasAirbags, batteryName: batteryName)
}
}
Опять же, давайте построим новые автомобили с помощью CarBuilder
:
var builder = CarBuilder()
let defaultCar = builder.buildCar()
print(defaultCar)
/* prints
color: UIExtendedSRGBColorSpace 1 0 0 1
Number of seats: 5
Number of Wheels: 4
Type: automatic
Motor: Motor(id: "111", name: "Default Motor", model: "T9", numberOfCylinders: 4)
Airbag Availability: false
Battery Name: Default Battery Name
*/
builder.batteryName = "New Battery Name"
let editedBatteryCar = builder.buildCar()
print(editedBatteryCar)
/*
color: UIExtendedSRGBColorSpace 1 0 0 1
Number of seats: 5
Number of Wheels: 4
Type: automatic
Motor: Motor(id: "111", name: "Default Motor", model: "T9", numberOfCylinders: 4)
Airbag Availability: false
Battery Name: New Battery Name
*/