Le pattern Builder en Swift

Aujourd’hui nous allons démarrer une nouvelle série de tutoriel consacrés aux designs patterns (ou patrons de conception en français). Pour commencer cette série nous allons parler du Builder pattern (ou monteur en français) qui permet la création d’objet complexe de manière élégante. Il existe plusieurs variations de ce pattern mais en swift une version semble ce dégagé des autres.

Tout d’abord posons le cadre. Prenons l’example d’une voiture :

import UIKit

class Voiture {
  let modèle: String
  let année: UInt
  let nombreDePorte: UInt
  let couleur: UIColor
}

Ici nous avons défini quelques propriétés (immuable) qui caractérisent une voiture à savoir le modèle de la voiture, son année de production, le nombre de portière et sa couleur. Nous aurions pu en définir beaucoup plus mais pour la simplicité de ce tutoriel nous nous sommes limité à ces 4 propriétés.

La méthode « traditionnelle » pour créer une voiture serait de créer un initialiser permettant de définir les 4 propriétés :

init(modèle: String, année: UInt, nombreDePorte: UInt, couleur: UIColor) {
  self.modèle        = modèle
  self.année         = année
  self.nombreDePorte = nombreDePorte
  self.couleur       = couleur
}

Rien de bien compliqué jusqu’a maintenant, mais imaginons qu’à la place nous avions une dizaine ou une centaine de propriété définissant la voiture, vous imaginer la taille de l’initialiseur ?! Donc comme vous l’avez compris ici le builder pattern va nous êtres très utile. Pour ce faire nous allons en premier lieux créer un objet builder :

class VoitureBuilder {
  // 1
  typealias BuilderClosure = (VoitureBuilder) -> ()

  // 2
  var modèle: String      = "Clio 4"
  var année: UInt         = 2016
  var nombreDePorte: UInt = 3
  var couleur: UIColor    = .whiteColor()

  // 3
  init() {}

  // 4
  init(buildClosure: BuilderClosure) {
    buildClosure(self)
  }
}

Décortiquons un peu tout ça :

  1. Nous définissons une closure (ou un block) qui prends en paramètre une VoitureBuilder et ne retourne rien. Nous en aurons besoin après dans le constructeur.
  2. Ici nous définissons l’ensemble des variables nécessaire à la construction d’une voiture. Deux écoles s’affrontent : ceux qui définissent les variables optionnelles et ceux qui préfèrent définir des valeurs par défaut. Nous n’avons pas de préférence mais il était plus simple ici de ne pas utiliser d’optionnelle.
  3. Comme nous avons définis des valeurs par défaut, nous créons un constructeur vide qui utilise ces valeurs par défaut.
  4. Ce constructeur est un peu étrange si on a du mal avec les closures. En réalité c’est très simple. Il prend en paramètre un BuilderClosure qui est appelé directement en passant une référence de lui même en argument. Cela nous permet de créer une API facile d’utilisateur que nous allons voir juste après.

Maintenant en lieu et place de l’initialiseur « init:année:nombreDePorte:couleur: » de la classe Voiture nous allons écrire ceci :

init(builder: VoitureBuilder) {
  self.modèle        = builder.modèle
  self.année         = builder.année
  self.nombreDePorte = builder.nombreDePorte
  self.couleur       = builder.couleur
}

Il ne nous reste plus qu’à créer une voiture. Pour cela on utilise un builder de voiture qu’on passe en paramètre :

let megane = VoitureBuilder { builder in
  builder.modèle        = "Mégane"
  builder.année         = 2012
  builder.nombreDePorte = 5
  builder.couleur       = .redColor()
}

let voiture = DeathStar(builder: megane)

Pour allez plus loin

Si vous souhaitez en savoir plus sur ce patron de conception vous pouvez jetez un coup d’oeil sur wikipédia.

Dans les prochaines semaines d’autres patterns seront détaillés ici, donc n’hésiter pas à vous abonner au blog ou à nous suivre sur twitter, facebook ou google+ pour avoir les dernières nouvelles.

À bientôt.

1 Etoile2 Etoiles3 Etoiles4 Etoiles5 Etoiles (1 votes, average: 5,00 out of 5)
Loading...
Share on FacebookShare on Google+Tweet about this on TwitterShare on LinkedInPin on PinterestShare on RedditDigg this

Aucun commentaire

Time limit is exhausted. Please reload CAPTCHA.