Recherche…


Introduction

Les modèles de conception sont des solutions générales aux problèmes fréquents dans le développement de logiciels. Les éléments suivants sont des modèles de meilleures pratiques normalisées en matière de structuration et de conception du code, ainsi que des exemples de contextes communs dans lesquels ces modèles de conception seraient appropriés.

Les motifs de conception créatifs abstraite l'instanciation des objets pour rendre un système plus indépendant du processus de création, de composition et de représentation.

Singleton

Les singletons sont un modèle de conception fréquemment utilisé qui consiste en une instance unique d'une classe partagée dans un programme.

Dans l'exemple suivant, nous créons une propriété static contenant une instance de la classe Foo . N'oubliez pas qu'une propriété static est partagée entre tous les objets d'une classe et ne peut pas être remplacée par un sous-classement.

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

Usage:

Foo.shared.doSomething()

Veillez à vous souvenir de l'initialiseur private :

Cela garantit que vos singletons sont vraiment uniques et empêche les objets extérieurs de créer leurs propres instances de votre classe grâce au contrôle d'accès. Comme tous les objets sont livrés avec un initialiseur public par défaut dans Swift, vous devez remplacer votre init et le rendre privé. KrakenDev

Méthode d'usine

Dans la programmation basée sur les classes, le modèle de méthode de fabrique est un modèle de création qui utilise des méthodes d'usine pour résoudre le problème de la création d'objets sans avoir à spécifier la classe exacte de l'objet à créer. Référence 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)

En faisant cela, nous ne dépendons pas de l'implémentation réelle de la classe, ce qui rend l' sender() complètement transparent à qui le consomme.

Dans ce cas, tout ce que nous devons savoir, c'est qu'un expéditeur va gérer la livraison et expose une méthode appelée send() . Il y a plusieurs autres avantages: réduire le couplage des classes, faciliter le test, ajouter de nouveaux comportements sans avoir à changer qui en consomme.

Dans la conception orientée objet, les interfaces fournissent des couches d'abstraction qui facilitent l'explication conceptuelle du code et créent une barrière empêchant les dépendances. Référence Wikipedia

Observateur

Le motif de l'observateur est l'endroit où un objet, appelé le sujet, maintient une liste de ses dépendants, appelés observateurs, et les avertit automatiquement de tout changement d'état, généralement en appelant l'une de leurs méthodes. Il est principalement utilisé pour mettre en œuvre des systèmes de gestion d'événements distribués. Le modèle Observer est également un élément clé du modèle architectural familier modèle-vue-contrôleur (MVC). Référence Wikipedia

Fondamentalement, le modèle d'observateur est utilisé lorsque vous avez un objet qui peut avertir les observateurs de certains comportements ou changements d'état.

Tout d'abord, créons une référence globale (en dehors d'une classe) pour le Centre de notifications:

let notifCentre = NotificationCenter.default

Super maintenant, nous pouvons appeler cela de n'importe où. Nous voudrions alors enregistrer une classe en tant qu'observateur ...

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

Cela ajoute la classe en tant qu'observateur pour "readForMyFunc". Il indique également que la fonction myFunc doit être appelée lorsque cette notification est reçue. Cette fonction doit être écrite dans la même classe:

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

L'un des avantages de ce modèle est que vous pouvez ajouter de nombreuses classes en tant qu'observateurs et effectuer ainsi de nombreuses actions après une notification.

La notification peut maintenant être simplement envoyée (ou affichée si vous préférez) depuis presque n'importe où dans le code avec la ligne:

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

Vous pouvez également transmettre des informations avec la notification en tant que dictionnaire

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

Mais alors vous devez ajouter une notification à votre fonction:

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
}

Chaîne de responsabilité

Dans la conception orientée objet, le modèle de chaîne de responsabilité est un modèle de conception constitué d'une source d'objets de command et d'une série d'objets de processing . Chaque objet de processing contient une logique qui définit les types d'objets de commande qu'il peut gérer; les autres sont transmis au prochain objet de processing de la chaîne. Un mécanisme existe également pour ajouter de nouveaux objets de processing à la fin de cette chaîne. Wikipédia

Mise en place des cours constituant la chaîne de responsabilité.

Nous créons d'abord une interface pour tous les objets de 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)
    }
  }
}

Ensuite, nous créons l'objet de command .

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

Enfin, créer des objets constituant la chaîne de 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?
}

Initier et enchaîner ensemble:

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

manager.successor = director
director.successor = president

Le mécanisme pour chaîner des objets ici est l'accès à la propriété

Création d'une requête pour l'exécuter:

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

Itérateur

En programmation informatique, un itérateur est un objet qui permet à un programmeur de traverser un conteneur, en particulier des listes. Wikipédia

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

Et exemple d'utilisation serait

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

Motif de constructeur

Le modèle de générateur est un modèle de conception de logiciel de création d'objet . Contrairement au modèle de fabrique abstrait et au modèle de méthode de fabrique dont l’intention est de permettre le polymorphisme, l’intention du modèle de générateur est de trouver une solution à l’anti-pattern du constructeur télescopique. L'anti-modèle du constructeur télescopique se produit lorsque l'augmentation de la combinaison de paramètres du constructeur d'objet mène à une liste exponentielle de constructeurs. Au lieu d'utiliser de nombreux constructeurs, le modèle de générateur utilise un autre objet, un générateur, qui reçoit chaque paramètre d'initialisation étape par étape et renvoie ensuite l'objet construit résultant.

-Wikipédia

L'objectif principal du modèle de générateur est de configurer une configuration par défaut pour un objet depuis sa création. C'est un intermédiaire entre l'objet à construire et tous les autres objets liés à sa construction.

Exemple:

Pour le rendre plus clair, examinons un exemple de constructeur de voiture .

Considérez que nous avons une classe Car contient de nombreuses options pour créer un objet, telles que:

  • Couleur.
  • Nombre de places.
  • Nombre de roues
  • Type.
  • Type d'engin
  • Moteur.
  • Disponibilité des airbags.
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
        
    }
}

Créer un objet de voiture:

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

Le problème se pose lors de la création d'un objet de voiture: la voiture nécessite la création de nombreuses données de configuration.

Pour appliquer le modèle de générateur, les paramètres d'initialisation doivent avoir des valeurs par défaut modifiables si nécessaire .

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 définit les propriétés pouvant être modifiées pour modifier les valeurs de l'objet de voiture créé.

Construisons de nouvelles voitures en utilisant le 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
*/

L' avantage de l'application du modèle de générateur est la facilité de création d'objets qui doivent contenir beaucoup de configurations en définissant des valeurs par défaut, ainsi que la facilité de modification de ces valeurs par défaut.

Continuer:

Comme bonne pratique, toutes les propriétés nécessitant des valeurs par défaut doivent être dans un protocole séparé , qui doit être implémenté par la classe elle-même et son générateur.

En s'appuyant sur notre exemple, créons un nouveau protocole appelé 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)
    }
}

L'avantage de déclarer les propriétés nécessitant une valeur par défaut dans un protocole est de forcer l'implémentation de toute nouvelle propriété ajoutée. Lorsqu'une classe est conforme à un protocole, elle doit déclarer toutes ses propriétés / méthodes.

Considérez qu'il y a une nouvelle fonctionnalité requise qui devrait être ajoutée au modèle de création d'une voiture appelée "nom de la batterie":

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

Après avoir ajouté la nouvelle propriété, notez que deux erreurs de compilation se produiront, notifiant que la conformité au protocole CarBluePrint nécessite de déclarer la propriété 'batteryName'. Cela garantit que CarBuilder déclarera et définira une valeur par défaut pour la propriété batteryName .

Après avoir ajouté batteryName nouvelle propriété à CarBluePrint protocole, la mise en œuvre des deux Car et CarBuilder classes devraient être:

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

Encore une fois, construisons de nouvelles voitures en utilisant le 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
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow