Ricerca…


introduzione

I modelli di progettazione sono soluzioni generali ai problemi che si verificano frequentemente nello sviluppo del software. I seguenti sono modelli di migliori pratiche standardizzate nella strutturazione e progettazione del codice, nonché esempi di contesti comuni in cui questi modelli di progettazione sarebbero appropriati.

I modelli di progettazione creazionali astraggono l'istanziazione degli oggetti per rendere un sistema più indipendente dal processo di creazione, composizione e rappresentazione.

Singleton

I singleton sono uno schema di progettazione frequentemente utilizzato che consiste in una singola istanza di una classe condivisa in tutto il programma.

Nell'esempio seguente, creiamo una proprietà static che contiene un'istanza della classe Foo . Ricorda che una proprietà static è condivisa tra tutti gli oggetti di una classe e non può essere sovrascritta dalla sottoclasse.

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

Uso:

Foo.shared.doSomething()

Assicurati di ricordare l'inizializzatore private :

Questo assicura che i tuoi singleton siano veramente unici e impedisce agli oggetti esterni di creare le proprie istanze della tua classe attraverso il controllo degli accessi. Poiché tutti gli oggetti sono dotati di un inizializzatore pubblico predefinito in Swift, è necessario sovrascrivere il tuo init e renderlo privato. KrakenDev

Metodo di fabbrica

Nella programmazione basata su classi, lo schema del metodo factory è uno schema creativo che utilizza i metodi factory per affrontare il problema della creazione di oggetti senza dover specificare la classe esatta dell'oggetto che verrà creato. Riferimento di 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)

Facendo ciò non dipendiamo dalla reale implementazione della classe, rendendo il sender() completamente trasparente a chi lo sta consumando.

In questo caso tutto ciò che dobbiamo sapere è che un mittente gestirà la consegna e esporrà un metodo chiamato send() . Ci sono molti altri vantaggi: ridurre le classi di accoppiamento, più facile da testare, più facile aggiungere nuovi comportamenti senza dover cambiare chi lo sta consumando.

All'interno della progettazione orientata agli oggetti, le interfacce forniscono livelli di astrazione che facilitano la spiegazione concettuale del codice e creano una barriera che impedisce le dipendenze. Riferimento di Wikipedia

Osservatore

Lo schema dell'osservatore è dove un oggetto, chiamato soggetto, mantiene una lista dei suoi dipendenti, chiamati osservatori, e li notifica automaticamente di qualsiasi cambiamento di stato, di solito chiamando uno dei loro metodi. Viene principalmente utilizzato per implementare sistemi di gestione degli eventi distribuiti. Il pattern Observer è anche una parte fondamentale nel modello di architettura modello-vista-controller (MVC) familiare. Riferimento di Wikipedia

Fondamentalmente il pattern di osservatore viene utilizzato quando si dispone di un oggetto che può informare gli osservatori di determinati comportamenti o cambiamenti di stato.

In primo luogo consente di creare un riferimento globale (al di fuori di una classe) per il Centro di notifica:

let notifCentre = NotificationCenter.default

Ottimo ora possiamo chiamarlo da qualsiasi luogo. Vorremmo quindi registrare una lezione come osservatore ...

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

Questo aggiunge la classe come osservatore per "readForMyFunc". Indica anche che la funzione myFunc deve essere chiamata quando viene ricevuta la notifica. Questa funzione dovrebbe essere scritta nella stessa classe:

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

Uno dei vantaggi di questo schema è che è possibile aggiungere molte classi come osservatori e quindi eseguire molte azioni dopo una notifica.

La notifica può ora essere semplicemente inviata (o pubblicata se preferisci) da quasi ovunque nel codice con la linea:

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

È anche possibile passare informazioni con la notifica come dizionario

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

Ma poi devi aggiungere una notifica alla tua funzione:

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
}

Catena di responsabilità

Nella progettazione orientata agli oggetti, il modello della catena di responsabilità è un modello di progettazione costituito da una sorgente di oggetti di command e una serie di oggetti di processing . Ogni oggetto di processing contiene la logica che definisce i tipi di oggetti comando che può gestire; il resto viene passato al successivo oggetto di processing nella catena. Esiste anche un meccanismo per aggiungere nuovi oggetti di processing alla fine di questa catena. Wikipedia

Impostare le classi che costituivano la catena di responsabilità.

Per prima cosa creiamo un'interfaccia per tutti gli oggetti di 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)
    }
  }
}

Quindi creiamo l'oggetto command .

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

Infine, creando oggetti che costituivano la catena di responsabilità.

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

Iniziare e incatenarlo insieme:

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

manager.successor = director
director.successor = president

Il meccanismo per concatenare oggetti qui è l'accesso alla proprietà

Creazione della richiesta per eseguirlo:

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

Nella programmazione del computer, un iteratore è un oggetto che consente a un programmatore di attraversare un contenitore, in particolare gli elenchi. 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)
  }
}

E l'esempio di utilizzo sarebbe

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

Modello costruttore

Il modello di builder è un modello di progettazione di software per la creazione di oggetti . A differenza del pattern factory astratto e del pattern method factory la cui intenzione è quella di abilitare il polimorfismo, l'intenzione del pattern builder è quella di trovare una soluzione all'anti-pattern del costruttore telescopico. L'anti-pattern del costruttore telescopico si verifica quando l'aumento della combinazione di parametri del costruttore oggetto porta a un elenco esponenziale di costruttori. Anziché utilizzare numerosi costruttori, il pattern di builder utilizza un altro oggetto, un builder, che riceve ogni parametro di inizializzazione passo dopo passo e quindi restituisce l'oggetto costruito risultante in una volta.

-Wikipedia

L'obiettivo principale del modello di builder è impostare una configurazione predefinita per un oggetto dalla sua creazione. È un intermediario tra l'oggetto sarà costruito e tutti gli altri oggetti relativi alla sua costruzione.

Esempio:

Per rendere più chiaro, diamo un'occhiata a un esempio di Car Builder .

Considera che abbiamo una classe Car contiene molte opzioni per creare un oggetto, come ad esempio:

  • Colore.
  • Numero di posti.
  • Numero di ruote
  • Genere.
  • Tipo di attrezzo.
  • Il motore.
  • Disponibilità di airbag
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
        
    }
}

Creare un oggetto auto:

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

Il problema sorge quando si crea un oggetto auto è che l'auto richiede la creazione di molti dati di configurazione.

Per applicare il Pattern Builder, i parametri di inizializzazione dovrebbero avere valori predefiniti che sono modificabili se necessario .

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

La classe CarBuilder definisce le proprietà che possono essere modificate per modificare i valori dell'oggetto auto creato.

Costruiamo nuove auto usando il 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
*/

Il vantaggio dell'applicazione del modello di builder è la facilità di creare oggetti che dovrebbero contenere molte configurazioni impostando valori predefiniti, inoltre, la facilità di modificare questi valori predefiniti.

Prendilo ulteriormente:

Come buona pratica, tutte le proprietà che hanno bisogno di valori di default dovrebbero essere in un protocollo separato , che dovrebbe essere implementato dalla classe stessa e dal suo costruttore.

Seguendo il nostro esempio, creiamo un nuovo protocollo chiamato 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)
    }
}

Il vantaggio di dichiarare le proprietà che necessitano di un valore predefinito in un protocollo è la forzatura di implementare qualsiasi nuova proprietà aggiunta; Quando una classe si conforma a un protocollo, deve dichiarare tutte le sue proprietà / metodi.

Considera che c'è una nuova funzione richiesta che dovrebbe essere aggiunta al progetto di creazione di un'auto chiamata "nome batteria":

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

Dopo aver aggiunto la nuova proprietà, notare che si verificheranno due errori in fase di compilazione, notificando che la conformità al protocollo CarBluePrint richiede la dichiarazione della proprietà 'batteryName'. Ciò garantisce che CarBuilder dichiarerà e imposterà un valore predefinito per la proprietà batteryName .

Dopo aver aggiunto batteryName nuova proprietà CarBluePrint protocollo, l'attuazione di entrambe le Car e CarBuilder classi dovrebbe essere:

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

Ancora una volta, costruiamo nuove auto usando il 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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow