Поиск…


Вступление

Шаблоны проектирования - это общие решения проблем, которые часто возникают при разработке программного обеспечения. Ниже приведены шаблоны стандартизованных передовых методов структурирования и проектирования кода, а также примеры общих контекстов, в которых эти шаблоны проектирования были бы подходящими.

Созданные шаблоны проектирования абстрагируют создание объектов, чтобы сделать систему более независимой от процесса создания, составления и представления.

одиночка

Синглтоны - это часто используемый шаблон проектирования, который состоит из одного экземпляра класса, который используется во всей программе.

В следующем примере мы создаем 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

Шаблон построителя представляет собой шаблон разработки программного обеспечения для создания объектов . В отличие от абстрактного заводского шаблона и шаблона фабричного метода, целью которого является включение полиморфизма, намерение шаблона-строителя состоит в том, чтобы найти решение для анти-шаблона конструктора телескопа. Конструкция антивибратора конструктора телескопов возникает, когда увеличение комбинации параметров конструктора объекта приводит к экспоненциальному списку конструкторов. Вместо использования множества конструкторов шаблон построителя использует другой объект, строитель, который по шагам получает каждый параметр инициализации и затем возвращает результирующий построенный объект одновременно.

-Wikipedia

Основная цель шаблона построителя - установить конфигурацию по умолчанию для объекта из его создания. Это посредник между объектом будет построен и все другие объекты, связанные с его построением.

Пример:

Чтобы это стало понятным, давайте посмотрим на пример 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
 */


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow