Sök…


Introduktion

Designmönster är allmänna lösningar på problem som ofta uppstår i mjukvaruutvecklingen. Följande är mallar för standardiserade bästa metoder för strukturering och design av kod, samt exempel på vanliga sammanhang där dessa designmönster skulle vara lämpliga.

Skapande designmönster abstraherar inställningen av objekt för att göra ett system mer oberoende av processen för skapande, komposition och representation.

Singleton

Singletons är ett ofta använt designmönster som består av en enda instans av en klass som delas genom ett program.

I följande exempel skapar vi en static egenskap som innehåller en instans av Foo klassen. Kom ihåg att en static egenskap delas mellan alla objekt i en klass och inte kan skrivas över genom underklassificering.

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

Användande:

Foo.shared.doSomething()

Kom ihåg att komma ihåg den private initieraren:

Detta säkerställer att dina singletoner verkligen är unika och förhindrar externa objekt från att skapa sina egna instanser av din klass genom tillträde till åtkomstkontroll. Eftersom alla objekt har en standardinitierare i Swift måste du åsidosätta din init och göra den privat. KrakenDev

Fabriksmetod

I klassbaserad programmering är fabriksmetodmönstret ett skapande mönster som använder fabriksmetoder för att hantera problemet med att skapa objekt utan att behöva ange den exakta klassen för objektet som ska skapas. Wikipedia-referens

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)

Genom att göra det beror vi inte på den verkliga implementeringen av klassen, vilket gör sender() helt transparent för vem som konsumerar den.

I detta fall behöver vi bara veta att en avsändare kommer att hantera leveransen och exponerar en metod som heter send() . Det finns flera andra fördelar: minska klassen koppling, lättare att testa, lättare att lägga till nya beteenden utan att behöva ändra vem som konsumerar det.

Inom objektorienterad design ger gränssnitt lager av abstraktion som underlättar konceptuell förklaring av koden och skapar en barriär som förhindrar beroenden. Wikipedia-referens

Observatör

Observatörsmönstret är där ett objekt, kallat subjektet, upprätthåller en lista över dets beroende, kallas observatörer och meddelar dem automatiskt om alla tillståndsförändringar, vanligtvis genom att kalla en av deras metoder. Det används främst för att implementera distribuerade evenemangshanteringssystem. Observer-mönstret är också en viktig del i det välbekanta arkitekturmönstret MVC (model-view – controller). Wikipedia-referens

I grund och botten används observatörsmönstret när du har ett objekt som kan meddela observatörer om vissa beteenden eller tillståndsförändringar.

Låt oss först skapa en global referens (utanför en klass) för meddelandecentret:

let notifCentre = NotificationCenter.default

Bra nu kan vi kalla det var som helst. Vi skulle då vilja registrera en klass som observatör ...

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

Detta lägger klassen till som observatör för "readForMyFunc". Det indikerar också att funktionen myFunc ska anropas när den meddelandet tas emot. Denna funktion ska skrivas i samma klass:

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

En av fördelarna med detta mönster är att du kan lägga till många klasser som observatörer och därmed utföra många åtgärder efter en anmälan.

Meddelandet kan nu helt enkelt skickas (eller publiceras om du föredrar det) från nästan var som helst i koden med raden:

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

Du kan också skicka information med meddelandet som en ordlista

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

Men då måste du lägga till ett meddelande till din funktion:

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
}

Ansvarskedja

I objektorienterad design är kedjan av ansvarsmönster ett designmönster som består av en källa för command och en serie processing . Varje processing innehåller logik som definierar typerna av kommandobjekt som det kan hantera; resten överförs till nästa processing i kedjan. En mekanism finns också för att lägga till nya processing i slutet av denna kedja. Wikipedia

Ställa in klasser som utgör kedjan av ansvar.

Först skapar vi ett gränssnitt för alla 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)
    }
  }
}

Sedan skapar vi command .

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

Slutligen, skapa objekt som utgör kedjan av ansvar.

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

Initiera och kedja det tillsammans:

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

manager.successor = director
director.successor = president

Mekanismen för att kedja upp objekt här är tillgång till fastigheter

Skapar begäran om att köra den:

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

Vid datorprogrammering är en iterator ett objekt som gör det möjligt för en programmerare att korsa en behållare, särskilt listor. 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)
  }
}

Och användningsexempel skulle vara

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 mönster

Byggmästarmönstret är ett designmönster för programvara för objektskapande . Till skillnad från det abstrakta fabriksmönstret och fabriksmetodmönstret vars avsikt är att möjliggöra polymorfism, är avsikten med byggmönstret att hitta en lösning på teleskopkonstruktorns antimönster. Teleskopkonstruktorns antimönster uppstår när ökningen av objektkonstruktörens parameterkombination leder till en exponentiell lista över konstruktörer. I stället för att använda många konstruktörer använder byggmästarmönstret ett annat objekt, en byggare, som tar emot varje initieringsparameter steg för steg och sedan returnerar det resulterande konstruerade objektet på en gång.

-Wikipedia

Huvudmålet med byggmönster är att ställa in en standardkonfiguration för ett objekt från skapandet. Det är en mellanhand mellan objektet kommer att byggas och alla andra objekt relaterade till att bygga det.

Exempel:

För att göra det mer tydligt, låt oss ta en titt på ett exempel på bilbyggare .

Tänk på att vi har en bilklass som innehåller många alternativ för att skapa ett objekt, till exempel:

  • Färg.
  • Antal platser.
  • Antal hjul.
  • Typ.
  • Växeltyp.
  • Motor.
  • Airbag tillgänglighet.
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
        
    }
}

Skapa ett bilobjekt:

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

Problemet uppstår när du skapar ett bilobjekt är att bilen kräver att många konfigurationsdata skapas.

För att tillämpa Builder-mönstret bör initialiseringsparametrarna ha standardvärden som kan ändras vid behov .

CarBuilder-klass:

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 definierar egenskaper som kan ändras för att redigera värdena på det skapade bilobjektet.

Låt oss bygga nya bilar med hjälp av 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
*/

Fördelen med att tillämpa Builder-mönstret är att det är enkelt att skapa objekt som borde innehålla mycket av konfigurationer genom att ställa in standardvärden, också lättheten att ändra dessa standardvärden.

Ta det längre:

Som en bra praxis bör alla egenskaper som behöver standardvärden vara i ett separat protokoll som ska implementeras av klassen själv och dess byggare.

Tillbaka till vårt exempel, låt oss skapa ett nytt protokoll som heter 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)
    }
}

Fördelen med att deklarera de egenskaper som behöver standardvärde till ett protokoll är att tvinga till att implementera alla nya tillagda egenskaper; När en klass överensstämmer med ett protokoll måste den förklara alla dess egenskaper / metoder.

Tänk på att det krävs en ny funktion som bör läggas till planen för att skapa en bil som heter "batterinamn":

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

Efter att du har lagt till den nya egenskapen bör du notera att två kompileringstidsfel kommer att uppstå och meddela att överensstämmelse med CarBluePrint protokollet kräver att deklarera egenskapen "batterinamn". Det garanterar att CarBuilder kommer att deklarera och ställa in ett standardvärde för batteryName egenskapen.

Efter att batteryName lagt till ny egenskap till CarBluePrint protokollet CarBluePrint implementeringen av både Car och CarBuilder klasser vara:

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

Låt oss åter bygga nya bilar med hjälp av 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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow