Swift Language
Design Patterns - Créatif
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 deprocessing
. Chaque objet deprocessing
contient une logique qui définit les types d'objets de commande qu'il peut gérer; les autres sont transmis au prochain objet deprocessing
de la chaîne. Un mécanisme existe également pour ajouter de nouveaux objets deprocessing
à 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.
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
*/