Szukaj…


Wprowadzenie

Wzorce projektowe są ogólnymi rozwiązaniami problemów, które często występują podczas tworzenia oprogramowania. Poniżej znajdują się szablony znormalizowanych najlepszych praktyk w zakresie strukturyzacji i projektowania kodu, a także przykłady typowych kontekstów, w których te wzorce projektowe byłyby odpowiednie.

Wzorce projektowania kreują abstrakcję tworzenia obiektów, aby system był bardziej niezależny od procesu tworzenia, kompozycji i reprezentacji.

Singel

Singletony to często używany wzorzec projektowy, który składa się z pojedynczej instancji klasy współdzielonej przez program.

W poniższym przykładzie tworzymy właściwość static która przechowuje instancję klasy Foo . Pamiętaj, że właściwość static jest wspólna dla wszystkich obiektów klasy i nie może zostać zastąpiona przez podklasę.

public class Foo
{
    static let shared = Foo()
    
    // Used for preventing the class from being instantiated directly
    private init() {}
    
    func doSomething()
    {
        print("Do something")
    }
}

Stosowanie:

Foo.shared.doSomething()

Pamiętaj, aby zapamiętać private inicjator:

Dzięki temu twoje singletony są naprawdę wyjątkowe i zapobiegają tworzeniu przez obiekty zewnętrzne własnych instancji twojej klasy dzięki kontroli dostępu. Ponieważ wszystkie obiekty mają domyślny inicjalizator publiczny w Swift, musisz przesłonić swój init i ustawić go jako prywatny. KrakenDev

Metoda fabryczna

W programowaniu klasowym wzorzec metody fabrycznej jest wzorcem kreacyjnym, który wykorzystuje metody fabryczne do rozwiązania problemu tworzenia obiektów bez konieczności określania dokładnej klasy obiektu, który zostanie utworzony. Odniesienie do Wikipedii

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)

W ten sposób nie jesteśmy zależni od rzeczywistej implementacji klasy, dzięki czemu sender() całkowicie przejrzysty dla tego, kto go używa.

W tym przypadku wszystko, co musimy wiedzieć, to to, że nadawca obsłuży dostarczenie i ujawni metodę o nazwie send() . Istnieje kilka innych zalet: ograniczenie łączenia klas, łatwiejsze do testowania, łatwiejsze dodawanie nowych zachowań bez konieczności zmiany, kto je konsumuje.

W projektowaniu zorientowanym obiektowo interfejsy zapewniają warstwy abstrakcji, które ułatwiają koncepcyjne objaśnienie kodu i tworzą barierę zapobiegającą zależnościom. Odniesienie do Wikipedii

Obserwator

Wzorzec obserwatora polega na tym, że obiekt, zwany podmiotem, przechowuje listę swoich osób zależnych, zwanych obserwatorami, i powiadamia ich automatycznie o wszelkich zmianach stanu, zwykle przez wywołanie jednej z ich metod. Służy głównie do wdrażania rozproszonych systemów obsługi zdarzeń. Wzorzec Observer jest także kluczowym elementem znanego wzorca architektonicznego model-view-controller (MVC). Odniesienie do Wikipedii

Zasadniczo wzorzec obserwatora jest używany, gdy masz obiekt, który może powiadamiać obserwatorów o niektórych zachowaniach lub zmianach stanu.

Najpierw stwórzmy globalne odwołanie (poza klasą) dla Centrum powiadomień:

let notifCentre = NotificationCenter.default

Świetnie, teraz możemy to nazwać z dowolnego miejsca. Chcielibyśmy wtedy zarejestrować klasę jako obserwatora ...

notifCentre.addObserver(self, selector: #selector(self.myFunc), name: "myNotification", object: nil)

To dodaje klasę jako obserwatora dla „readForMyFunc”. Wskazuje również, że funkcja myFunc powinna zostać wywołana po otrzymaniu tego powiadomienia. Ta funkcja powinna być napisana w tej samej klasie:

func myFunc(){
    print("The notification has been received")
}

Jedną z zalet tego wzorca jest to, że można dodać wiele klas jako obserwatorów, a tym samym wykonać wiele akcji po jednym powiadomieniu.

Powiadomienie można teraz po prostu wysłać (lub opublikować, jeśli wolisz) z dowolnego miejsca w kodzie za pomocą wiersza:

notifCentre.post(name: "myNotification", object: nil)

Możesz również przekazać informacje wraz z powiadomieniem jako Słownik

let myInfo = "pass this on"
notifCentre.post(name: "myNotification", object: ["moreInfo":myInfo])

Ale musisz dodać powiadomienie do swojej funkcji:

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
}

Łańcuch odpowiedzialności

W projektowaniu obiektowym wzorzec łańcucha odpowiedzialności to wzorzec projektowy składający się ze źródła obiektów command i szeregu obiektów processing . Każdy obiekt processing zawiera logikę, która określa typy obiektów poleceń, które może obsługiwać; reszta jest przekazywana do następnego obiektu processing w łańcuchu. Istnieje również mechanizm dodawania nowych obiektów processing na końcu tego łańcucha. Wikipedia

Utworzenie klas, które stanowiły łańcuch odpowiedzialności.

Najpierw tworzymy interfejs dla wszystkich processing obiektów.

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)
    }
  }
}

Następnie tworzymy obiekt command .

struct PurchaseRequest {
  var amount : Float
  var purpose : String
}

Wreszcie, tworzenie obiektów stanowiących łańcuch odpowiedzialności.

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?
}

Zainicjuj i połącz to razem:

let manager = ManagerPower()
let director = DirectorPower()
let president = PresidentPower()

manager.successor = director
director.successor = president

Mechanizm łączenia łańcuchów obiektów tutaj to dostęp do właściwości

Tworzenie żądania, aby je uruchomić:

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

Iterator

W programowaniu komputerowym iterator to obiekt, który umożliwia programiście przechodzenie przez kontener, w szczególności listy. Wikipedia

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)
  }
}

I przykładem użycia byłoby

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)")
}

Wzór konstruktora

Wzorzec konstruktora to wzorzec projektowy oprogramowania do tworzenia obiektów . W przeciwieństwie do abstrakcyjnego wzorca fabrycznego i wzorca metody fabrycznej, którego celem jest umożliwienie polimorfizmu, intencją wzorca konstruktora jest znalezienie rozwiązania anty-wzorca teleskopowego konstruktora. Anty-wzorzec konstruktora teleskopowego występuje, gdy wzrost kombinacji parametrów konstruktora obiektów prowadzi do wykładniczej listy konstruktorów. Zamiast używać wielu konstruktorów, wzorzec konstruktora używa innego obiektu, konstruktora, który odbiera każdy parametr inicjalizacji krok po kroku, a następnie zwraca powstały skonstruowany obiekt naraz.

-Wikipedia

Głównym celem wzorca konstruktora jest ustawienie domyślnej konfiguracji obiektu od momentu jego utworzenia. Jest to pośrednik między obiektem, który zostanie zbudowany, a wszystkimi innymi obiektami związanymi z jego budowaniem.

Przykład:

Aby to wyjaśnić, spójrzmy na przykład konstruktora samochodów .

Weź pod uwagę, że mamy klasę Car, która zawiera wiele opcji tworzenia obiektu, takich jak:

  • Kolor.
  • Ilość miejsc.
  • Ilość kół
  • Rodzaj.
  • Rodzaj przekładni
  • Silnik.
  • Dostępność poduszek powietrznych.
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
        
    }
}

Tworzenie obiektu samochodowego:

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
*/

Problem powstający podczas tworzenia obiektu samochodu polega na tym, że samochód wymaga utworzenia wielu danych konfiguracyjnych.

Aby zastosować wzorzec konstruktora, parametry inicjatora powinny mieć wartości domyślne, które w razie potrzeby można zmieniać .

Klasa 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)
    }
}

Klasa CarBuilder definiuje właściwości, które można zmienić, aby edytować wartości utworzonego obiektu samochodu.

CarBuilder nowe samochody za pomocą 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
*/

Zaletą zastosowania Wzorca konstruktora jest łatwość tworzenia obiektów, które powinny zawierać wiele konfiguracji poprzez ustawienie wartości domyślnych, a także łatwość zmiany tych wartości domyślnych.

Przejdź dalej:

Jako dobrą praktykę wszystkie właściwości wymagające wartości domyślnych powinny znajdować się w oddzielnym protokole , który powinien być implementowany przez samą klasę i jej konstruktora.

Wracając do naszego przykładu, stwórzmy nowy protokół o nazwie 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)
    }
}

Korzyścią z zadeklarowania właściwości wymagających wartości domyślnej w protokole jest wymuszenie implementacji każdej nowej dodanej właściwości; Gdy klasa jest zgodna z protokołem, musi zadeklarować wszystkie swoje właściwości / metody.

Weź pod uwagę, że do planu tworzenia samochodu o nazwie „nazwa akumulatora” należy dodać nową funkcję:

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 }
}

Po dodaniu nowej właściwości należy pamiętać, że wystąpią dwa błędy czasu kompilacji, informujące, że zgodność z protokołem CarBluePrint wymaga zadeklarowania właściwości „batteryName”. To gwarantuje, że CarBuilder zadeklaruje i ustawi wartość domyślną dla właściwości batteryName .

Po dodaniu batteryName nową właściwość CarBluePrint protokołu, realizacja obu Car i CarBuilder klas powinny być:

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)
    }
}

Ponownie CarBuilder nowe samochody za pomocą 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
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow