项目作者: ochococo

项目描述 :
在Swift 5.0中实现的设计模式
高级语言: Swift
项目地址: git://github.com/ochococo/Design-Patterns-In-Swift.git
创建时间: 2014-07-13T18:15:14Z
项目社区:https://github.com/ochococo/Design-Patterns-In-Swift

开源协议:GNU General Public License v3.0

下载


Design Patterns implemented in Swift 5.0

A short cheat-sheet with Xcode 10.2 Playground (Design-Patterns.playground.zip).

🇨🇳中文版

👷 Project started by: @nsmeme (Oktawian Chojnacki)

👷 中文版由 @binglogo (棒棒彬) 整理翻译。

🚀 How to generate README, Playground and zip from source: CONTRIBUTING.md

  1. print("Welcome!")

Table of Contents

Behavioral Creational Structural
🐝 Chain Of Responsibility 🌰 Abstract Factory 🔌 Adapter
👫 Command 👷 Builder 🌉 Bridge
🎶 Interpreter 🏭 Factory Method 🌿 Composite
🍫 Iterator 🔂 Monostate 🍧 Decorator
💐 Mediator 🃏 Prototype 🎁 Façade
💾 Memento 💍 Singleton 🍃 Flyweight
👓 Observer ☔ Protection Proxy
🐉 State 🍬 Virtual Proxy
💡 Strategy
📝 Template Method
🏃 Visitor

Behavioral

In software engineering, behavioral design patterns are design patterns that identify common communication patterns between objects and realize these patterns. By doing so, these patterns increase flexibility in carrying out this communication.

Source: wikipedia.org

🐝 Chain Of Responsibility

The chain of responsibility pattern is used to process varied requests, each of which may be dealt with by a different handler.

Example:

  1. protocol Withdrawing {
  2. func withdraw(amount: Int) -> Bool
  3. }
  4. final class MoneyPile: Withdrawing {
  5. let value: Int
  6. var quantity: Int
  7. var next: Withdrawing?
  8. init(value: Int, quantity: Int, next: Withdrawing?) {
  9. self.value = value
  10. self.quantity = quantity
  11. self.next = next
  12. }
  13. func withdraw(amount: Int) -> Bool {
  14. var amount = amount
  15. func canTakeSomeBill(want: Int) -> Bool {
  16. return (want / self.value) > 0
  17. }
  18. var quantity = self.quantity
  19. while canTakeSomeBill(want: amount) {
  20. if quantity == 0 {
  21. break
  22. }
  23. amount -= self.value
  24. quantity -= 1
  25. }
  26. guard amount > 0 else {
  27. return true
  28. }
  29. if let next {
  30. return next.withdraw(amount: amount)
  31. }
  32. return false
  33. }
  34. }
  35. final class ATM: Withdrawing {
  36. private var hundred: Withdrawing
  37. private var fifty: Withdrawing
  38. private var twenty: Withdrawing
  39. private var ten: Withdrawing
  40. private var startPile: Withdrawing {
  41. return self.hundred
  42. }
  43. init(hundred: Withdrawing,
  44. fifty: Withdrawing,
  45. twenty: Withdrawing,
  46. ten: Withdrawing) {
  47. self.hundred = hundred
  48. self.fifty = fifty
  49. self.twenty = twenty
  50. self.ten = ten
  51. }
  52. func withdraw(amount: Int) -> Bool {
  53. return startPile.withdraw(amount: amount)
  54. }
  55. }

Usage

  1. // Create piles of money and link them together 10 < 20 < 50 < 100.**
  2. let ten = MoneyPile(value: 10, quantity: 6, next: nil)
  3. let twenty = MoneyPile(value: 20, quantity: 2, next: ten)
  4. let fifty = MoneyPile(value: 50, quantity: 2, next: twenty)
  5. let hundred = MoneyPile(value: 100, quantity: 1, next: fifty)
  6. // Build ATM.
  7. var atm = ATM(hundred: hundred, fifty: fifty, twenty: twenty, ten: ten)
  8. atm.withdraw(amount: 310) // Cannot because ATM has only 300
  9. atm.withdraw(amount: 100) // Can withdraw - 1x100

👫 Command

The command pattern is used to express a request, including the call to be made and all of its required parameters, in a command object. The command may then be executed immediately or held for later use.

Example:

  1. protocol DoorCommand {
  2. func execute() -> String
  3. }
  4. final class OpenCommand: DoorCommand {
  5. let doors:String
  6. required init(doors: String) {
  7. self.doors = doors
  8. }
  9. func execute() -> String {
  10. return "Opened \(doors)"
  11. }
  12. }
  13. final class CloseCommand: DoorCommand {
  14. let doors:String
  15. required init(doors: String) {
  16. self.doors = doors
  17. }
  18. func execute() -> String {
  19. return "Closed \(doors)"
  20. }
  21. }
  22. final class HAL9000DoorsOperations {
  23. let openCommand: DoorCommand
  24. let closeCommand: DoorCommand
  25. init(doors: String) {
  26. self.openCommand = OpenCommand(doors:doors)
  27. self.closeCommand = CloseCommand(doors:doors)
  28. }
  29. func close() -> String {
  30. return closeCommand.execute()
  31. }
  32. func open() -> String {
  33. return openCommand.execute()
  34. }
  35. }

Usage:

  1. let podBayDoors = "Pod Bay Doors"
  2. let doorModule = HAL9000DoorsOperations(doors:podBayDoors)
  3. doorModule.open()
  4. doorModule.close()

🎶 Interpreter

The interpreter pattern is used to evaluate sentences in a language.

Example

  1. protocol IntegerExpression {
  2. func evaluate(_ context: IntegerContext) -> Int
  3. func replace(character: Character, integerExpression: IntegerExpression) -> IntegerExpression
  4. func copied() -> IntegerExpression
  5. }
  6. final class IntegerContext {
  7. private var data: [Character:Int] = [:]
  8. func lookup(name: Character) -> Int {
  9. return self.data[name]!
  10. }
  11. func assign(expression: IntegerVariableExpression, value: Int) {
  12. self.data[expression.name] = value
  13. }
  14. }
  15. final class IntegerVariableExpression: IntegerExpression {
  16. let name: Character
  17. init(name: Character) {
  18. self.name = name
  19. }
  20. func evaluate(_ context: IntegerContext) -> Int {
  21. return context.lookup(name: self.name)
  22. }
  23. func replace(character name: Character, integerExpression: IntegerExpression) -> IntegerExpression {
  24. if name == self.name {
  25. return integerExpression.copied()
  26. } else {
  27. return IntegerVariableExpression(name: self.name)
  28. }
  29. }
  30. func copied() -> IntegerExpression {
  31. return IntegerVariableExpression(name: self.name)
  32. }
  33. }
  34. final class AddExpression: IntegerExpression {
  35. private var operand1: IntegerExpression
  36. private var operand2: IntegerExpression
  37. init(op1: IntegerExpression, op2: IntegerExpression) {
  38. self.operand1 = op1
  39. self.operand2 = op2
  40. }
  41. func evaluate(_ context: IntegerContext) -> Int {
  42. return self.operand1.evaluate(context) + self.operand2.evaluate(context)
  43. }
  44. func replace(character: Character, integerExpression: IntegerExpression) -> IntegerExpression {
  45. return AddExpression(op1: operand1.replace(character: character, integerExpression: integerExpression),
  46. op2: operand2.replace(character: character, integerExpression: integerExpression))
  47. }
  48. func copied() -> IntegerExpression {
  49. return AddExpression(op1: self.operand1, op2: self.operand2)
  50. }
  51. }

Usage

  1. var context = IntegerContext()
  2. var a = IntegerVariableExpression(name: "A")
  3. var b = IntegerVariableExpression(name: "B")
  4. var c = IntegerVariableExpression(name: "C")
  5. var expression = AddExpression(op1: a, op2: AddExpression(op1: b, op2: c)) // a + (b + c)
  6. context.assign(expression: a, value: 2)
  7. context.assign(expression: b, value: 1)
  8. context.assign(expression: c, value: 3)
  9. var result = expression.evaluate(context)

🍫 Iterator

The iterator pattern is used to provide a standard interface for traversing a collection of items in an aggregate object without the need to understand its underlying structure.

Example:

  1. struct Novella {
  2. let name: String
  3. }
  4. struct Novellas {
  5. let novellas: [Novella]
  6. }
  7. struct NovellasIterator: IteratorProtocol {
  8. private var current = 0
  9. private let novellas: [Novella]
  10. init(novellas: [Novella]) {
  11. self.novellas = novellas
  12. }
  13. mutating func next() -> Novella? {
  14. defer { current += 1 }
  15. return novellas.count > current ? novellas[current] : nil
  16. }
  17. }
  18. extension Novellas: Sequence {
  19. func makeIterator() -> NovellasIterator {
  20. return NovellasIterator(novellas: novellas)
  21. }
  22. }

Usage

  1. let greatNovellas = Novellas(novellas: [Novella(name: "The Mist")] )
  2. for novella in greatNovellas {
  3. print("I've read: \(novella)")
  4. }

💐 Mediator

The mediator pattern is used to reduce coupling between classes that communicate with each other. Instead of classes communicating directly, and thus requiring knowledge of their implementation, the classes send messages via a mediator object.

Example

  1. protocol Receiver {
  2. associatedtype MessageType
  3. func receive(message: MessageType)
  4. }
  5. protocol Sender {
  6. associatedtype MessageType
  7. associatedtype ReceiverType: Receiver
  8. var recipients: [ReceiverType] { get }
  9. func send(message: MessageType)
  10. }
  11. struct Programmer: Receiver {
  12. let name: String
  13. init(name: String) {
  14. self.name = name
  15. }
  16. func receive(message: String) {
  17. print("\(name) received: \(message)")
  18. }
  19. }
  20. final class MessageMediator: Sender {
  21. internal var recipients: [Programmer] = []
  22. func add(recipient: Programmer) {
  23. recipients.append(recipient)
  24. }
  25. func send(message: String) {
  26. for recipient in recipients {
  27. recipient.receive(message: message)
  28. }
  29. }
  30. }

Usage

  1. func spamMonster(message: String, worker: MessageMediator) {
  2. worker.send(message: message)
  3. }
  4. let messagesMediator = MessageMediator()
  5. let user0 = Programmer(name: "Linus Torvalds")
  6. let user1 = Programmer(name: "Avadis 'Avie' Tevanian")
  7. messagesMediator.add(recipient: user0)
  8. messagesMediator.add(recipient: user1)
  9. spamMonster(message: "I'd Like to Add you to My Professional Network", worker: messagesMediator)

💾 Memento

The memento pattern is used to capture the current state of an object and store it in such a manner that it can be restored at a later time without breaking the rules of encapsulation.

Example

  1. typealias Memento = [String: String]

Originator

  1. protocol MementoConvertible {
  2. var memento: Memento { get }
  3. init?(memento: Memento)
  4. }
  5. struct GameState: MementoConvertible {
  6. private enum Keys {
  7. static let chapter = "com.valve.halflife.chapter"
  8. static let weapon = "com.valve.halflife.weapon"
  9. }
  10. var chapter: String
  11. var weapon: String
  12. init(chapter: String, weapon: String) {
  13. self.chapter = chapter
  14. self.weapon = weapon
  15. }
  16. init?(memento: Memento) {
  17. guard let mementoChapter = memento[Keys.chapter],
  18. let mementoWeapon = memento[Keys.weapon] else {
  19. return nil
  20. }
  21. chapter = mementoChapter
  22. weapon = mementoWeapon
  23. }
  24. var memento: Memento {
  25. return [ Keys.chapter: chapter, Keys.weapon: weapon ]
  26. }
  27. }

Caretaker

  1. enum CheckPoint {
  2. private static let defaults = UserDefaults.standard
  3. static func save(_ state: MementoConvertible, saveName: String) {
  4. defaults.set(state.memento, forKey: saveName)
  5. defaults.synchronize()
  6. }
  7. static func restore(saveName: String) -> Any? {
  8. return defaults.object(forKey: saveName)
  9. }
  10. }

Usage

  1. var gameState = GameState(chapter: "Black Mesa Inbound", weapon: "Crowbar")
  2. gameState.chapter = "Anomalous Materials"
  3. gameState.weapon = "Glock 17"
  4. CheckPoint.save(gameState, saveName: "gameState1")
  5. gameState.chapter = "Unforeseen Consequences"
  6. gameState.weapon = "MP5"
  7. CheckPoint.save(gameState, saveName: "gameState2")
  8. gameState.chapter = "Office Complex"
  9. gameState.weapon = "Crossbow"
  10. CheckPoint.save(gameState, saveName: "gameState3")
  11. if let memento = CheckPoint.restore(saveName: "gameState1") as? Memento {
  12. let finalState = GameState(memento: memento)
  13. dump(finalState)
  14. }

👓 Observer

The observer pattern is used to allow an object to publish changes to its state.
Other objects subscribe to be immediately notified of any changes.

Example

  1. protocol PropertyObserver : class {
  2. func willChange(propertyName: String, newPropertyValue: Any?)
  3. func didChange(propertyName: String, oldPropertyValue: Any?)
  4. }
  5. final class TestChambers {
  6. weak var observer:PropertyObserver?
  7. private let testChamberNumberName = "testChamberNumber"
  8. var testChamberNumber: Int = 0 {
  9. willSet(newValue) {
  10. observer?.willChange(propertyName: testChamberNumberName, newPropertyValue: newValue)
  11. }
  12. didSet {
  13. observer?.didChange(propertyName: testChamberNumberName, oldPropertyValue: oldValue)
  14. }
  15. }
  16. }
  17. final class Observer : PropertyObserver {
  18. func willChange(propertyName: String, newPropertyValue: Any?) {
  19. if newPropertyValue as? Int == 1 {
  20. print("Okay. Look. We both said a lot of things that you're going to regret.")
  21. }
  22. }
  23. func didChange(propertyName: String, oldPropertyValue: Any?) {
  24. if oldPropertyValue as? Int == 0 {
  25. print("Sorry about the mess. I've really let the place go since you killed me.")
  26. }
  27. }
  28. }

Usage

  1. var observerInstance = Observer()
  2. var testChambers = TestChambers()
  3. testChambers.observer = observerInstance
  4. testChambers.testChamberNumber += 1

🐉 State

The state pattern is used to alter the behaviour of an object as its internal state changes.
The pattern allows the class for an object to apparently change at run-time.

Example

  1. final class Context {
  2. private var state: State = UnauthorizedState()
  3. var isAuthorized: Bool {
  4. get { return state.isAuthorized(context: self) }
  5. }
  6. var userId: String? {
  7. get { return state.userId(context: self) }
  8. }
  9. func changeStateToAuthorized(userId: String) {
  10. state = AuthorizedState(userId: userId)
  11. }
  12. func changeStateToUnauthorized() {
  13. state = UnauthorizedState()
  14. }
  15. }
  16. protocol State {
  17. func isAuthorized(context: Context) -> Bool
  18. func userId(context: Context) -> String?
  19. }
  20. class UnauthorizedState: State {
  21. func isAuthorized(context: Context) -> Bool { return false }
  22. func userId(context: Context) -> String? { return nil }
  23. }
  24. class AuthorizedState: State {
  25. let userId: String
  26. init(userId: String) { self.userId = userId }
  27. func isAuthorized(context: Context) -> Bool { return true }
  28. func userId(context: Context) -> String? { return userId }
  29. }

Usage

  1. let userContext = Context()
  2. (userContext.isAuthorized, userContext.userId)
  3. userContext.changeStateToAuthorized(userId: "admin")
  4. (userContext.isAuthorized, userContext.userId) // now logged in as "admin"
  5. userContext.changeStateToUnauthorized()
  6. (userContext.isAuthorized, userContext.userId)

💡 Strategy

The strategy pattern is used to create an interchangeable family of algorithms from which the required process is chosen at run-time.

Example

  1. struct TestSubject {
  2. let pupilDiameter: Double
  3. let blushResponse: Double
  4. let isOrganic: Bool
  5. }
  6. protocol RealnessTesting: AnyObject {
  7. func testRealness(_ testSubject: TestSubject) -> Bool
  8. }
  9. final class VoightKampffTest: RealnessTesting {
  10. func testRealness(_ testSubject: TestSubject) -> Bool {
  11. return testSubject.pupilDiameter < 30.0 || testSubject.blushResponse == 0.0
  12. }
  13. }
  14. final class GeneticTest: RealnessTesting {
  15. func testRealness(_ testSubject: TestSubject) -> Bool {
  16. return testSubject.isOrganic
  17. }
  18. }
  19. final class BladeRunner {
  20. private let strategy: RealnessTesting
  21. init(test: RealnessTesting) {
  22. self.strategy = test
  23. }
  24. func testIfAndroid(_ testSubject: TestSubject) -> Bool {
  25. return !strategy.testRealness(testSubject)
  26. }
  27. }

Usage

  1. let rachel = TestSubject(pupilDiameter: 30.2,
  2. blushResponse: 0.3,
  3. isOrganic: false)
  4. // Deckard is using a traditional test
  5. let deckard = BladeRunner(test: VoightKampffTest())
  6. let isRachelAndroid = deckard.testIfAndroid(rachel)
  7. // Gaff is using a very precise method
  8. let gaff = BladeRunner(test: GeneticTest())
  9. let isDeckardAndroid = gaff.testIfAndroid(rachel)

📝 Template Method

The template method pattern defines the steps of an algorithm and allows the redefinition of one or more of these steps. In this way, the template method protects the algorithm, the order of execution and provides abstract methods that can be implemented by concrete types.

Example

  1. protocol Garden {
  2. func prepareSoil()
  3. func plantSeeds()
  4. func waterPlants()
  5. func prepareGarden()
  6. }
  7. extension Garden {
  8. func prepareGarden() {
  9. prepareSoil()
  10. plantSeeds()
  11. waterPlants()
  12. }
  13. }
  14. final class RoseGarden: Garden {
  15. func prepare() {
  16. prepareGarden()
  17. }
  18. func prepareSoil() {
  19. print ("prepare soil for rose garden")
  20. }
  21. func plantSeeds() {
  22. print ("plant seeds for rose garden")
  23. }
  24. func waterPlants() {
  25. print ("water the rose garden")
  26. }
  27. }

Usage

  1. let roseGarden = RoseGarden()
  2. roseGarden.prepare()

🏃 Visitor

The visitor pattern is used to separate a relatively complex set of structured data classes from the functionality that may be performed upon the data that they hold.

Example

  1. protocol PlanetVisitor {
  2. func visit(planet: PlanetAlderaan)
  3. func visit(planet: PlanetCoruscant)
  4. func visit(planet: PlanetTatooine)
  5. func visit(planet: MoonJedha)
  6. }
  7. protocol Planet {
  8. func accept(visitor: PlanetVisitor)
  9. }
  10. final class MoonJedha: Planet {
  11. func accept(visitor: PlanetVisitor) { visitor.visit(planet: self) }
  12. }
  13. final class PlanetAlderaan: Planet {
  14. func accept(visitor: PlanetVisitor) { visitor.visit(planet: self) }
  15. }
  16. final class PlanetCoruscant: Planet {
  17. func accept(visitor: PlanetVisitor) { visitor.visit(planet: self) }
  18. }
  19. final class PlanetTatooine: Planet {
  20. func accept(visitor: PlanetVisitor) { visitor.visit(planet: self) }
  21. }
  22. final class NameVisitor: PlanetVisitor {
  23. var name = ""
  24. func visit(planet: PlanetAlderaan) { name = "Alderaan" }
  25. func visit(planet: PlanetCoruscant) { name = "Coruscant" }
  26. func visit(planet: PlanetTatooine) { name = "Tatooine" }
  27. func visit(planet: MoonJedha) { name = "Jedha" }
  28. }

Usage

  1. let planets: [Planet] = [PlanetAlderaan(), PlanetCoruscant(), PlanetTatooine(), MoonJedha()]
  2. let names = planets.map { (planet: Planet) -> String in
  3. let visitor = NameVisitor()
  4. planet.accept(visitor: visitor)
  5. return visitor.name
  6. }
  7. names

Creational

In software engineering, creational design patterns are design patterns that deal with object creation mechanisms, trying to create objects in a manner suitable to the situation. The basic form of object creation could result in design problems or added complexity to the design. Creational design patterns solve this problem by somehow controlling this object creation.

Source: wikipedia.org

🌰 Abstract Factory

The abstract factory pattern is used to provide a client with a set of related or dependant objects.
The “family” of objects created by the factory are determined at run-time.

Example

Protocols

  1. protocol BurgerDescribing {
  2. var ingredients: [String] { get }
  3. }
  4. struct CheeseBurger: BurgerDescribing {
  5. let ingredients: [String]
  6. }
  7. protocol BurgerMaking {
  8. func make() -> BurgerDescribing
  9. }
  10. // Number implementations with factory methods
  11. final class BigKahunaBurger: BurgerMaking {
  12. func make() -> BurgerDescribing {
  13. return CheeseBurger(ingredients: ["Cheese", "Burger", "Lettuce", "Tomato"])
  14. }
  15. }
  16. final class JackInTheBox: BurgerMaking {
  17. func make() -> BurgerDescribing {
  18. return CheeseBurger(ingredients: ["Cheese", "Burger", "Tomato", "Onions"])
  19. }
  20. }

Abstract factory

  1. enum BurgerFactoryType: BurgerMaking {
  2. case bigKahuna
  3. case jackInTheBox
  4. func make() -> BurgerDescribing {
  5. switch self {
  6. case .bigKahuna:
  7. return BigKahunaBurger().make()
  8. case .jackInTheBox:
  9. return JackInTheBox().make()
  10. }
  11. }
  12. }

Usage

  1. let bigKahuna = BurgerFactoryType.bigKahuna.make()
  2. let jackInTheBox = BurgerFactoryType.jackInTheBox.make()

👷 Builder

The builder pattern is used to create complex objects with constituent parts that must be created in the same order or using a specific algorithm.
An external class controls the construction algorithm.

Example

  1. final class DeathStarBuilder {
  2. var x: Double?
  3. var y: Double?
  4. var z: Double?
  5. typealias BuilderClosure = (DeathStarBuilder) -> ()
  6. init(buildClosure: BuilderClosure) {
  7. buildClosure(self)
  8. }
  9. }
  10. struct DeathStar : CustomStringConvertible {
  11. let x: Double
  12. let y: Double
  13. let z: Double
  14. init?(builder: DeathStarBuilder) {
  15. if let x = builder.x, let y = builder.y, let z = builder.z {
  16. self.x = x
  17. self.y = y
  18. self.z = z
  19. } else {
  20. return nil
  21. }
  22. }
  23. var description:String {
  24. return "Death Star at (x:\(x) y:\(y) z:\(z))"
  25. }
  26. }

Usage

  1. let empire = DeathStarBuilder { builder in
  2. builder.x = 0.1
  3. builder.y = 0.2
  4. builder.z = 0.3
  5. }
  6. let deathStar = DeathStar(builder:empire)

🏭 Factory Method

The factory pattern is used to replace class constructors, abstracting the process of object generation so that the type of the object instantiated can be determined at run-time.

Example

  1. protocol CurrencyDescribing {
  2. var symbol: String { get }
  3. var code: String { get }
  4. }
  5. final class Euro: CurrencyDescribing {
  6. var symbol: String {
  7. return "€"
  8. }
  9. var code: String {
  10. return "EUR"
  11. }
  12. }
  13. final class UnitedStatesDolar: CurrencyDescribing {
  14. var symbol: String {
  15. return "$"
  16. }
  17. var code: String {
  18. return "USD"
  19. }
  20. }
  21. enum Country {
  22. case unitedStates
  23. case spain
  24. case uk
  25. case greece
  26. }
  27. enum CurrencyFactory {
  28. static func currency(for country: Country) -> CurrencyDescribing? {
  29. switch country {
  30. case .spain, .greece:
  31. return Euro()
  32. case .unitedStates:
  33. return UnitedStatesDolar()
  34. default:
  35. return nil
  36. }
  37. }
  38. }

Usage

  1. let noCurrencyCode = "No Currency Code Available"
  2. CurrencyFactory.currency(for: .greece)?.code ?? noCurrencyCode
  3. CurrencyFactory.currency(for: .spain)?.code ?? noCurrencyCode
  4. CurrencyFactory.currency(for: .unitedStates)?.code ?? noCurrencyCode
  5. CurrencyFactory.currency(for: .uk)?.code ?? noCurrencyCode

🔂 Monostate

The monostate pattern is another way to achieve singularity. It works through a completely different mechanism, it enforces the behavior of singularity without imposing structural constraints.
So in that case, monostate saves the state as static instead of the entire instance as a singleton.
SINGLETON and MONOSTATE - Robert C. Martin

Example:

  1. class Settings {
  2. enum Theme {
  3. case `default`
  4. case old
  5. case new
  6. }
  7. private static var theme: Theme?
  8. var currentTheme: Theme {
  9. get { Settings.theme ?? .default }
  10. set(newTheme) { Settings.theme = newTheme }
  11. }
  12. }

Usage:

  1. import SwiftUI
  2. // When change the theme
  3. let settings = Settings() // Starts using theme .old
  4. settings.currentTheme = .new // Change theme to .new
  5. // On screen 1
  6. let screenColor: Color = Settings().currentTheme == .old ? .gray : .white
  7. // On screen 2
  8. let screenTitle: String = Settings().currentTheme == .old ? "Itunes Connect" : "App Store Connect"

🃏 Prototype

The prototype pattern is used to instantiate a new object by copying all of the properties of an existing object, creating an independent clone.
This practise is particularly useful when the construction of a new object is inefficient.

Example

  1. class MoonWorker {
  2. let name: String
  3. var health: Int = 100
  4. init(name: String) {
  5. self.name = name
  6. }
  7. func clone() -> MoonWorker {
  8. return MoonWorker(name: name)
  9. }
  10. }

Usage

  1. let prototype = MoonWorker(name: "Sam Bell")
  2. var bell1 = prototype.clone()
  3. bell1.health = 12
  4. var bell2 = prototype.clone()
  5. bell2.health = 23
  6. var bell3 = prototype.clone()
  7. bell3.health = 0

💍 Singleton

The singleton pattern ensures that only one object of a particular class is ever created.
All further references to objects of the singleton class refer to the same underlying instance.
There are very few applications, do not overuse this pattern!

Example:

  1. final class ElonMusk {
  2. static let shared = ElonMusk()
  3. private init() {
  4. // Private initialization to ensure just one instance is created.
  5. }
  6. }

Usage:

  1. let elon = ElonMusk.shared // There is only one Elon Musk folks.

Structural

In software engineering, structural design patterns are design patterns that ease the design by identifying a simple way to realize relationships between entities.

Source: wikipedia.org

🔌 Adapter

The adapter pattern is used to provide a link between two otherwise incompatible types by wrapping the “adaptee” with a class that supports the interface required by the client.

Example

  1. protocol NewDeathStarSuperLaserAiming {
  2. var angleV: Double { get }
  3. var angleH: Double { get }
  4. }

Adaptee

  1. struct OldDeathStarSuperlaserTarget {
  2. let angleHorizontal: Float
  3. let angleVertical: Float
  4. init(angleHorizontal: Float, angleVertical: Float) {
  5. self.angleHorizontal = angleHorizontal
  6. self.angleVertical = angleVertical
  7. }
  8. }

Adapter

  1. struct NewDeathStarSuperlaserTarget: NewDeathStarSuperLaserAiming {
  2. private let target: OldDeathStarSuperlaserTarget
  3. var angleV: Double {
  4. return Double(target.angleVertical)
  5. }
  6. var angleH: Double {
  7. return Double(target.angleHorizontal)
  8. }
  9. init(_ target: OldDeathStarSuperlaserTarget) {
  10. self.target = target
  11. }
  12. }

Usage

  1. let target = OldDeathStarSuperlaserTarget(angleHorizontal: 14.0, angleVertical: 12.0)
  2. let newFormat = NewDeathStarSuperlaserTarget(target)
  3. newFormat.angleH
  4. newFormat.angleV

🌉 Bridge

The bridge pattern is used to separate the abstract elements of a class from the implementation details, providing the means to replace the implementation details without modifying the abstraction.

Example

  1. protocol Switch {
  2. var appliance: Appliance { get set }
  3. func turnOn()
  4. }
  5. protocol Appliance {
  6. func run()
  7. }
  8. final class RemoteControl: Switch {
  9. var appliance: Appliance
  10. func turnOn() {
  11. self.appliance.run()
  12. }
  13. init(appliance: Appliance) {
  14. self.appliance = appliance
  15. }
  16. }
  17. final class TV: Appliance {
  18. func run() {
  19. print("tv turned on");
  20. }
  21. }
  22. final class VacuumCleaner: Appliance {
  23. func run() {
  24. print("vacuum cleaner turned on")
  25. }
  26. }

Usage

  1. let tvRemoteControl = RemoteControl(appliance: TV())
  2. tvRemoteControl.turnOn()
  3. let fancyVacuumCleanerRemoteControl = RemoteControl(appliance: VacuumCleaner())
  4. fancyVacuumCleanerRemoteControl.turnOn()

🌿 Composite

The composite pattern is used to create hierarchical, recursive tree structures of related objects where any element of the structure may be accessed and utilised in a standard manner.

Example

Component

  1. protocol Shape {
  2. func draw(fillColor: String)
  3. }

Leafs

  1. final class Square: Shape {
  2. func draw(fillColor: String) {
  3. print("Drawing a Square with color \(fillColor)")
  4. }
  5. }
  6. final class Circle: Shape {
  7. func draw(fillColor: String) {
  8. print("Drawing a circle with color \(fillColor)")
  9. }
  10. }

Composite

  1. final class Whiteboard: Shape {
  2. private lazy var shapes = [Shape]()
  3. init(_ shapes: Shape...) {
  4. self.shapes = shapes
  5. }
  6. func draw(fillColor: String) {
  7. for shape in self.shapes {
  8. shape.draw(fillColor: fillColor)
  9. }
  10. }
  11. }

Usage:

  1. var whiteboard = Whiteboard(Circle(), Square())
  2. whiteboard.draw(fillColor: "Red")

🍧 Decorator

The decorator pattern is used to extend or alter the functionality of objects at run- time by wrapping them in an object of a decorator class.
This provides a flexible alternative to using inheritance to modify behaviour.

Example

  1. protocol CostHaving {
  2. var cost: Double { get }
  3. }
  4. protocol IngredientsHaving {
  5. var ingredients: [String] { get }
  6. }
  7. typealias BeverageDataHaving = CostHaving & IngredientsHaving
  8. struct SimpleCoffee: BeverageDataHaving {
  9. let cost: Double = 1.0
  10. let ingredients = ["Water", "Coffee"]
  11. }
  12. protocol BeverageHaving: BeverageDataHaving {
  13. var beverage: BeverageDataHaving { get }
  14. }
  15. struct Milk: BeverageHaving {
  16. let beverage: BeverageDataHaving
  17. var cost: Double {
  18. return beverage.cost + 0.5
  19. }
  20. var ingredients: [String] {
  21. return beverage.ingredients + ["Milk"]
  22. }
  23. }
  24. struct WhipCoffee: BeverageHaving {
  25. let beverage: BeverageDataHaving
  26. var cost: Double {
  27. return beverage.cost + 0.5
  28. }
  29. var ingredients: [String] {
  30. return beverage.ingredients + ["Whip"]
  31. }
  32. }

Usage:

  1. var someCoffee: BeverageDataHaving = SimpleCoffee()
  2. print("Cost: \(someCoffee.cost); Ingredients: \(someCoffee.ingredients)")
  3. someCoffee = Milk(beverage: someCoffee)
  4. print("Cost: \(someCoffee.cost); Ingredients: \(someCoffee.ingredients)")
  5. someCoffee = WhipCoffee(beverage: someCoffee)
  6. print("Cost: \(someCoffee.cost); Ingredients: \(someCoffee.ingredients)")

🎁 Façade

The facade pattern is used to define a simplified interface to a more complex subsystem.

Example

  1. final class Defaults {
  2. private let defaults: UserDefaults
  3. init(defaults: UserDefaults = .standard) {
  4. self.defaults = defaults
  5. }
  6. subscript(key: String) -> String? {
  7. get {
  8. return defaults.string(forKey: key)
  9. }
  10. set {
  11. defaults.set(newValue, forKey: key)
  12. }
  13. }
  14. }

Usage

  1. let storage = Defaults()
  2. // Store
  3. storage["Bishop"] = "Disconnect me. I’d rather be nothing"
  4. // Read
  5. storage["Bishop"]

🍃 Flyweight

The flyweight pattern is used to minimize memory usage or computational expenses by sharing as much as possible with other similar objects.

Example

  1. // Instances of SpecialityCoffee will be the Flyweights
  2. struct SpecialityCoffee {
  3. let origin: String
  4. }
  5. protocol CoffeeSearching {
  6. func search(origin: String) -> SpecialityCoffee?
  7. }
  8. // Menu acts as a factory and cache for SpecialityCoffee flyweight objects
  9. final class Menu: CoffeeSearching {
  10. private var coffeeAvailable: [String: SpecialityCoffee] = [:]
  11. func search(origin: String) -> SpecialityCoffee? {
  12. if coffeeAvailable.index(forKey: origin) == nil {
  13. coffeeAvailable[origin] = SpecialityCoffee(origin: origin)
  14. }
  15. return coffeeAvailable[origin]
  16. }
  17. }
  18. final class CoffeeShop {
  19. private var orders: [Int: SpecialityCoffee] = [:]
  20. private let menu: CoffeeSearching
  21. init(menu: CoffeeSearching) {
  22. self.menu = menu
  23. }
  24. func takeOrder(origin: String, table: Int) {
  25. orders[table] = menu.search(origin: origin)
  26. }
  27. func serve() {
  28. for (table, origin) in orders {
  29. print("Serving \(origin) to table \(table)")
  30. }
  31. }
  32. }

Usage

  1. let coffeeShop = CoffeeShop(menu: Menu())
  2. coffeeShop.takeOrder(origin: "Yirgacheffe, Ethiopia", table: 1)
  3. coffeeShop.takeOrder(origin: "Buziraguhindwa, Burundi", table: 3)
  4. coffeeShop.serve()

☔ Protection Proxy

The proxy pattern is used to provide a surrogate or placeholder object, which references an underlying object.
Protection proxy is restricting access.

Example

  1. protocol DoorOpening {
  2. func open(doors: String) -> String
  3. }
  4. final class HAL9000: DoorOpening {
  5. func open(doors: String) -> String {
  6. return ("HAL9000: Affirmative, Dave. I read you. Opened \(doors).")
  7. }
  8. }
  9. final class CurrentComputer: DoorOpening {
  10. private var computer: HAL9000!
  11. func authenticate(password: String) -> Bool {
  12. guard password == "pass" else {
  13. return false
  14. }
  15. computer = HAL9000()
  16. return true
  17. }
  18. func open(doors: String) -> String {
  19. guard computer != nil else {
  20. return "Access Denied. I'm afraid I can't do that."
  21. }
  22. return computer.open(doors: doors)
  23. }
  24. }

Usage

  1. let computer = CurrentComputer()
  2. let podBay = "Pod Bay Doors"
  3. computer.open(doors: podBay)
  4. computer.authenticate(password: "pass")
  5. computer.open(doors: podBay)

🍬 Virtual Proxy

The proxy pattern is used to provide a surrogate or placeholder object, which references an underlying object.
Virtual proxy is used for loading object on demand.

Example

  1. protocol HEVSuitMedicalAid {
  2. func administerMorphine() -> String
  3. }
  4. final class HEVSuit: HEVSuitMedicalAid {
  5. func administerMorphine() -> String {
  6. return "Morphine administered."
  7. }
  8. }
  9. final class HEVSuitHumanInterface: HEVSuitMedicalAid {
  10. lazy private var physicalSuit: HEVSuit = HEVSuit()
  11. func administerMorphine() -> String {
  12. return physicalSuit.administerMorphine()
  13. }
  14. }

Usage

  1. let humanInterface = HEVSuitHumanInterface()
  2. humanInterface.administerMorphine()

Info

📖 Descriptions from: Gang of Four Design Patterns Reference Sheet