Zoeken…


Invoering

Ontwerppatronen zijn algemene oplossingen voor problemen die vaak voorkomen bij softwareontwikkeling. Hierna volgen sjablonen van gestandaardiseerde best practices bij het structureren en ontwerpen van code, evenals voorbeelden van gemeenschappelijke contexten waarin deze ontwerppatronen geschikt zijn.

Creatieve ontwerppatronen abstraheren de instantiatie van objecten om een systeem onafhankelijker te maken van het proces van creatie, compositie en representatie.

eenling

Singletons zijn een vaak gebruikt ontwerppatroon dat bestaat uit een enkele instantie van een klasse die door een programma wordt gedeeld.

In het volgende voorbeeld maken we een static eigenschap die een instantie van de klasse Foo . Onthoud dat een static eigenschap wordt gedeeld tussen alle objecten van een klasse en niet kan worden overschreven door subklasse.

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

Gebruik:

Foo.shared.doSomething()

Vergeet niet de private initialisatie te onthouden:

Dit zorgt ervoor dat uw singletons echt uniek zijn en voorkomt dat externe objecten hun eigen instanties van uw klas kunnen creëren door middel van toegangscontrole. Aangezien alle objecten een standaard openbare initialisatie hebben in Swift, moet u uw init negeren en privé maken. KrakenDev

Fabriek methode

In op klassen gebaseerde programmering is het fabrieksmethodepatroon een creatiepatroon dat fabrieksmethoden gebruikt om het probleem van het maken van objecten aan te pakken zonder de exacte klasse van het object dat moet worden gemaakt, op te geven. Wikipedia referentie

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)

Hierdoor zijn we niet afhankelijk van de echte implementatie van de klasse, waardoor de sender() volledig transparant is voor wie het gebruikt.

In dit geval hoeven we alleen maar te weten dat een afzender de bezorging afhandelt en de methode send() blootlegt. Er zijn verschillende andere voordelen: het verminderen van klassenkoppeling, gemakkelijker te testen, gemakkelijker om nieuw gedrag toe te voegen zonder te hoeven veranderen wie het consumeert.

Binnen objectgeoriënteerd ontwerp bieden interfaces abstractielagen die een conceptuele verklaring van de code mogelijk maken en een barrière creëren die afhankelijkheden voorkomt. Wikipedia referentie

Waarnemer

Het waarnemerspatroon is waar een object, het onderwerp genoemd, een lijst bijhoudt van zijn afhankelijken, waarnemers genoemd, en hen automatisch op de hoogte brengt van eventuele statusveranderingen, meestal door een van hun methoden aan te roepen. Het wordt hoofdzakelijk gebruikt om gedistribueerde systemen voor gebeurtenisafhandeling te implementeren. Het waarnemerspatroon is ook een belangrijk onderdeel van het bekende architecturale patroon – view – controller (MVC). Wikipedia referentie

Kortom, het waarnemerspatroon wordt gebruikt wanneer u een object hebt dat waarnemers op de hoogte kan stellen van bepaald gedrag of veranderingen in de toestand.

Laten we eerst een algemene referentie (buiten een klasse) maken voor het Berichtencentrum:

let notifCentre = NotificationCenter.default

Geweldig nu kunnen we dit overal noemen. We willen dan een klas als waarnemer registreren ...

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

Dit voegt de klasse toe als waarnemer voor "readForMyFunc". Het geeft ook aan dat de functie myFunc moet worden aangeroepen wanneer die melding wordt ontvangen. Deze functie moet in dezelfde klasse worden geschreven:

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

Een van de voordelen van dit patroon is dat u veel klassen als waarnemers kunt toevoegen en dus na één melding veel acties kunt uitvoeren.

De melding kan nu eenvoudig worden verzonden (of gepost als u dat wilt) vanuit vrijwel elke plaats in de code met de regel:

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

U kunt ook informatie doorgeven met de melding als een woordenboek

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

Maar dan moet u een melding toevoegen aan uw functie:

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
}

Keten van verantwoordelijkheid

In objectgeoriënteerd ontwerp is het patroon van verantwoordelijkheidsketen een ontwerppatroon dat bestaat uit een bron van command en een reeks processing . Elk processing bevat logica die de typen opdrachtobjecten definieert die het aankan; de rest wordt doorgegeven aan het volgende processing in de keten. Er bestaat ook een mechanisme voor het toevoegen van nieuwe processing aan het einde van deze keten. Wikipedia

De klassen opzetten die de verantwoordelijkheidsketen vormden.

Eerst maken we een interface voor alle 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)
    }
  }
}

Vervolgens maken we het command .

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

Eindelijk, het creëren van objecten die de verantwoordelijkheidsketen vormden.

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

Initieer en keten het samen:

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

manager.successor = director
director.successor = president

Het mechanisme voor het koppelen van objecten hier is eigendomstoegang

Verzoek maken om het uit te voeren:

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

Bij computerprogrammering is een iterator een object waarmee een programmeur een container, met name lijsten, kan doorkruisen. 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)
  }
}

En gebruiksvoorbeeld zou zijn

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

Bouwer patroon

Het bouwpatroon is een ontwerppatroon voor software voor het maken van objecten . In tegenstelling tot het abstracte fabriekspatroon en het fabrieksmethodiepatroon waarvan het de bedoeling is polymorfisme mogelijk te maken, is de bedoeling van het bouwpatroon om een oplossing te vinden voor het telescopische constructor-antipatroon. Het antipatroon van de telescopische constructor treedt op wanneer de toename van de combinatie van objectconstructorparameters leidt tot een exponentiële lijst van constructors. In plaats van het gebruik van meerdere constructors, gebruikt het builderpatroon een ander object, een builder, die elke initialisatieparameter stap voor stap ontvangt en vervolgens het resulterende geconstrueerde object in één keer retourneert.

Wikipedia

Het hoofddoel van het bouwerspatroon is om een standaardconfiguratie in te stellen voor een object dat is gemaakt. Het is een intermediair tussen het te bouwen object en alle andere objecten die verband houden met het bouwen.

Voorbeeld:

Laten we voor de duidelijkheid een voorbeeld van een autobouwer bekijken .

Bedenk dat we een autoklasse hebben die veel opties bevat om een object te maken, zoals:

  • Kleur.
  • Aantal zitplaatsen.
  • Aantal wielen.
  • Type.
  • Type versnelling.
  • Motor.
  • Airbag beschikbaarheid.
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
        
    }
}

Een auto-object maken:

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

Het probleem ontstaat bij het maken van een autoobject dat de auto veel configuratiegegevens vereist om te worden gemaakt.

Voor het toepassen van het Builder-patroon moeten de initialisatieparameters standaardwaarden hebben die indien nodig kunnen worden gewijzigd .

CarBuilder-klasse:

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

De klasse CarBuilder definieert eigenschappen die kunnen worden gewijzigd om de waarden van het gemaakte auto-object te bewerken.

Laten we nieuwe auto's bouwen met de 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
*/

Het voordeel van het toepassen van het Builder-patroon is het gemak van het maken van objecten die veel configuraties moeten bevatten door standaardwaarden in te stellen, en het gemak van het wijzigen van deze standaardwaarden.

Breng het verder:

Als een goede praktijk moeten alle eigenschappen waarvoor standaardwaarden nodig zijn, in een gescheiden protocol worden opgenomen , dat door de klasse zelf en de bouwer moet worden geïmplementeerd.

Terug naar ons voorbeeld, laten we een nieuw protocol maken met de naam 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)
    }
}

Het voordeel van het declareren van de eigenschappen die standaardwaarde nodig hebben in een protocol, is het dwingen om nieuwe toegevoegde eigenschappen te implementeren; Wanneer een klasse voldoet aan een protocol, moet deze alle eigenschappen / methoden opgeven.

Overweeg dat er een vereiste nieuwe functie is die moet worden toegevoegd aan de blauwdruk van het maken van een auto met de naam "batterijnaam":

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

Nadat u de nieuwe eigenschap hebt toegevoegd, moet u er rekening mee houden dat er tijdens de compilatie twee fouten optreden die CarBluePrint dat voor het voldoen aan het CarBluePrint protocol de eigenschap 'batteryName' moet worden aangegeven. Dat garandeert dat CarBuilder zal verklaren en stel een standaardwaarde voor batteryName eigenschap.

Na het toevoegen van batteryName nieuwe woning aan CarBluePrint protocol, de uitvoering van beide Car en CarBuilder moeten klassen:

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

Laten we opnieuw nieuwe auto's bouwen met de 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
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow