Learning Design Pattern Again

Essential Principles

  1. Encapsulate What Varies
  2. Program to an Interface, not an Implementation
  3. Favor Compositions, not Inheritance

SOLID Principles

Single Responsibility Principle

A class should have just one reason to change.

Open/Closed Principle

Classes should be open for extension but closed for modification.

Liskov Substitution Principle

When extending a class, remember that you should be able to pass objects of the subclass in place of objects of the parent class without breaking the client code.

Interface Segregation Principle

Clients shouldn’t be forced to depend methods they no not use.

Dependency Inversion Principle

High-level classes shouldn’t depend on low-level classes. Both should depend on abstractions. Abstractions shouldn’t depend on details. Details should depend on abstractions.

Creational Design Patterns

Mnemonics: AB-FPS

Factory Method

Main actors:

  • CarFactory
  • Car

Example:

1
2
3
4
5
6
func produceCar(by factory: CarFactory) -> Car {
return factory.produce()
}

let carA = produceCar(by: CarFactoryA())
let carB = produceCar(by: CarFactoryB())

Abstract Factory

Main actors:

  • CarFactory
  • Engine, Wheel, CarSheel

Example:

1
2
3
4
5
6
7
8
9
func produceCar(by factory: CarFactory) -> Car {
let carSheel = factory.makeCarShell()
let engine = factory.makeEngine()
let wheel = factory.makeWheel()
return assumble(carSheel, engine, wheel)
}

let carA = produceCar(by: CarFactoryA())
let carB = produceCar(by: CarFactoryB())

Builder

Example:

1
2
3
4
5
6
7
8
let builder = BuilderA()
builder.reset()
builder.buildStepA()
builder.buildStepB()
let productA = builder.getProduct()

let director = Director(builder: BuilderB())
let productB = director.make()

Prototype

Main actors:

  • Prototype

Example:

1
2
let originalConfiguration = ProtoType()
let copiedConfig = originalConfiguration.clone()

Singleton

Main actors:

  • Singleton

Example:

1
2
3
4
let a = Singleton.shared
let b = Singleton.shared
let c = Singleton() //FatalError
a == b // true

Structural Design Patterns

Mnemonics: ABC-PDF-F

Adapter

Objective Adapter:

Class Adapter:

Main actors:

  • DataAdapter

Example:

1
2
3
4
5
let thirdService = ThirdService()
let businessData = BusinessData()
let adapterData = AdapterData(innerData: businessData)

thirdService.handle(data: adapterData)

Bridge

The Abstract and Implementation aren’t the principle we understand about programing languages, like interface or abstract class.

Say we have a sharing card that has two schemes and two business logics. They can be combined to 4 kinds of compositions. If we use inheritance to implement it, we need to create 4 new classes.

We can divide these differences into two inheritances separately. Now we term the scheme layer as abstract, the business logic layer as implementation.

Composition

Proxy

Main actors:

  • Proxy

Example:

1
2
3
4
5
6
7
let client = Client()
let service = ConcreteService()

let proxy = Proxy(service)

client.inject(service: proxy)

Decorator

Main actors:

  • Component
  • ConcreteComponent
  • BaseDecorator, DecoratorA, DecoratorB

Facade

Main actors:

  • Facade

Flyweight

Main actors:

  • ImageStorage: FlyweightFactory
  • Image: Flyweight

Behavioral Design Patterns

Mnemonics: COMMITS-CSV

Chain of Responsibility

Main actors:

  • Handler

Examples:

1
2
3
4
5
6
7
8
9
10
11
let paymentRequest = Request()

let authenticationHandler = HandlerA()
let encryptHandler = HandlerB()
let payHandler = HandlerC()

authenticationHandler.setNext(encryptHandler)
encryptHandler.setNext(payHandler)

authenticationHandler.handle(request: paymentRequest)

Observer

Main actors:

  • Publisher
  • Subscriber

Examples:

1
2
3
4
5
6
7
8
9
10
let publisher = Publisher()
let subscriberA = ConcreteSubscriberA()
let subscriberB = ConcreteSubscriberB()

publisher.subscribe(subscriberA)
publisher.subscribe(subscriberB)

// The state in the publisher changed.
publisher.notifyAllSubscribers()

Mediator

Main actors:

  • Mediator
  • ConcreteMediator

Examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
protocol Mediator {
func notify(_ sender: Any)
}

class ConcreteMediator: Mediator {
var componentA: ComponentA?
var componentB: ComponentB?

func notify(_ sender: Any) {
if sender == A {
componentB.operationB()
}
}

func operationA() {
notify(self)
}
}

class ComponentA: Mediator {
private weak var mediator: Mediator?
func doOperation() {
mediator.notify(self)
}
}

class ComponentB: Mediator {
private weak var mediator: Mediator?
func doOperation() {
mediator.notify(self)
}

func operationB() {

}
}

let componentA = ComponentA()
let componentB = componentB()

let mediator = ConcreteMediator()
componentA.mediator = mediator
componentB.mediator = mediator

componentA.operation()
// componentB will invoke the operationB

Memento

Implementation based on an intermediate interface.

Implementation with even stricter encapsulation.

Iterator

Template Method

State

Main actors:

  • Context
  • State

Examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
protocol State {
func operation1()
func operation2()
}

class Context {
var state: State
func change(state: State) {
self.state = state
}
func action1() {
state.operation1()
state.operation2()
}

func action2 {
state.operation2()
}
}

class StateA: State {
weak var context: Context?

func operation1() {
// Do something
context.change(state: StateB())
}

func operation2() {

}
}

class StateB: State {
weak var context: Context?

func operation1() {
}

func operation2() {
// Do something
context.change(state: StateA())
}
}

Command

Strategy

Visitor

Swift can use protocol extension to replace the Visitor pattern.

Using the second plan can avoid modifications to File types. The Visitor patter needs to modify the declaration of the FileA and FileB to add a new method func visit(by visitor: Visitor).

Conclusion

Various Factory Principles

Answer

  • Factory
    It’s an ambiguous term that stands for a function, method or class that supposed to be producing something.

  • Creation method
    Every result of a factory method pattern is a creation method but not necessarily the reverse.
    A method that returns an instance object.

  • Static creation method
    It’s a creation method declared as static. You can invoke the method on a class without instantiating an object.
    Static creation methods are just convenience construction methods. We shouldn’t term it as Static factory method, because the factory method relies on inheritance. If you make it static, you can no longer extend it in its subclasses, which defeats the purpose of factory method pattern.
    A static method that returns an instance object, usually wraps construction methods.

  • Simple factory pattern
    The simple factory pattern describes a class that has one creation method with a large conditional that based on method parameters chooses which product class to instantiate and then return.
    When the creation method becomes more complex, you may want to extract these conditions to individual subclasses. Once you do it several times, you might find that the whole thing turned to the classic factory method pattern.
    A class has one method that returns an instance object of several kinds of classes based on conditions.

  • Factory method pattern
    A class(or interface) that defines an instance method returning an instance object(type of class or interface). You can create subclasses for the factory class to produce different instance objects by overriding the creation method.

  • Abstract factory pattern
    Like the factory method pattern, but has multiple creation methods that produce a group of related objects.