这是一篇主观的文章,文字解释也尽量简单,写做目的是一次团队内的知识分享,想让不了解设计模式的同事迅速对这些生词混个脸熟。因此本文适合懂Swift语法,想快速了解23个设计模式大概在讲什么的同窗。html
前置知识 UML那些线java
工厂模式顾名思义,就像一个工厂生产你所须要的产品算法
也就是工厂问题想解决的原始问题。数据库
protocol Product {}
class ConcreteProductA: Product {}
class ConcreteProductB: Product {}
class Client {
func createProduct(type: Int) -> Product {
if type == 0 {
return ConcreteProductA()
} else {
return ConcreteProductB()
}
}
}
let c = Client()
c.createProduct(type: 0) // get ConcreteProductA
复制代码
从代码和UML能够看出,为了获得产品A,调用者Client
要同时依赖Product
, ConcreteProductA
和ConcreteProductB
,并亲自写一个建立产品的方法。编程
每当需求新增一个产品,就要改动到调用方Client
。若是这一堆建立代码若是能够抽离出去就行了,因而简单工厂出现了。swift
简单工厂就作了一件事,把Client要作的建立工做,挪到了另外一个类里。设计模式
protocol Product {}
class ConcreteProductA: Product {}
class ConcreteProductB: Product {}
class Client {
let s = Factory()
}
class Factory {
func createProduct(type: Int) -> Product {
if type == 0 {
return ConcreteProductA()
} else {
return ConcreteProductB()
}
}
}
let c = Client()
c.s.createProduct(type: 0) // get ConcreteProductA
复制代码
Factory
代替了Client
对具体Product
的依赖,那么当需求变化的时候,咱们再也不须要改动调用方。这当然有所进步,但没法避免的是,每次变更都要在createProduct的方法内部新增一个if-else分支,这显然违背了开闭原则。缓存
为了解决这个问题,咱们引入另外一个模式。安全
官方定义网络
定义一个建立对象的接口,让其子类本身决定实例化哪个工厂类,工厂模式使其建立过程延迟到子类进行。
protocol Product {}
class ConcreteProductA: Product {}
class ConcreteProductB: Product {}
class Client {
let f = Factory()
}
class Factory {
func createProduct() -> Product? { return nil } // 用于继承
func createProduct(type: Int) -> Product? { // 用于调用
if type == 0 {
return ConcreteFactoryA().createProduct()
} else {
return ConcreteFactoryB().createProduct()
}
}
}
class ConcreteFactoryA: Factory {
override func createProduct() -> Product? {
// ... 产品加工过程
return ConcreteProductA()
}
}
class ConcreteFactoryB: Factory {
override func createProduct() -> Product? {
// ... 产品加工过程
return ConcreteProductB()
}
}
let c = Client()
c.f.createProduct(type: 0) // get ConcreteProductA
复制代码
对于工厂方法的实现,有众多不一样的解法,好比Factory
只保留一个createProduct
让子类实现,让Client
来选择生成哪一个具体工厂实例;或是引入一个FactoryMaker的中间层,做为生产工厂的“简单工厂”。我这里采用的方式是Factory既做为工厂父类,让具体工厂决定生产生么产品,又做为接口类,让Client
能够经过依赖注入选择特定工厂。我这样作的目的是,在不引入新的中间层的状况下,最小化Client的依赖。
工厂方法在简单工厂的基础上作了两件事:
工厂方法的贡献在于,这样作虽然不能完美避免对一个if-else的扩展,可是这个扩展规模被极大限制住了(只须要new一个类)。
工厂方法着重点是解决了单一产品线的派生问题。那若是有多个相关产品线呢?
官方定义
提供一个建立一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
protocol ProductA {}
class ConcreteProductA1: ProductA {}
class ConcreteProductA2: ProductA {}
protocol ProductB {}
class ConcreteProductB1: ProductB {}
class ConcreteProductB2: ProductB {}
class Client {
let f = Factory()
}
class Factory {
func createProductA() -> ProductA? { return nil } // 用于继承
func createProductB() -> ProductB? { return nil } // 用于继承
func createProductA(type: Int) -> ProductA? { // 用于调用
if type == 0 {
return ConcreteFactory1().createProductA()
} else {
return ConcreteFactory2().createProductA()
}
}
func createProductB(type: Int) -> ProductB? { // 用于调用
if type == 0 {
return ConcreteFactory1().createProductB()
} else {
return ConcreteFactory2().createProductB()
}
}
}
class ConcreteFactory1: Factory {
override func createProductA() -> ProductA? {
// ... 产品加工过程
return ConcreteProductA1()
}
override func createProductB() -> ProductB? {
// ... 产品加工过程
return ConcreteProductB1()
}
}
class ConcreteFactory2: Factory {
override func createProductA() -> ProductA? {
// ... 产品加工过程
return ConcreteProductA2()
}
override func createProductB() -> ProductB? {
// ... 产品加工过程
return ConcreteProductB2()
}
}
let c = Client()
c.f.createProductA(type: 0) // get ConcreteProductA1
c.f.createProductA(type: 1) // get ConcreteProductA2
c.f.createProductB(type: 0) // get ConcreteProductB1
c.f.createProductB(type: 1) // get ConcreteProductB2
复制代码
图很吓人,其实很简单。
当咱们有两个相关的产品线ProductA
和ProductB
, 例如螺丝和螺母,他们派生出ProductA1
,ProductB1
和 ProductA2
,ProductB2
,前者咱们由工厂ConcreteFactory1
来制做,后者由 ConcreteFactory2
来制做。
对于Client
来讲,他只须要知道有一个抽象的工厂能同时生产ProductA
和ProductB
就好了,那就是图中的Factory。
重点来了,这个抽象的Factory
是经过“工厂方法”模式把构造过程延迟到子类执行的,也就是说,抽象工厂是创建在工厂方法的基础上的模式。
因此抽象工厂,换句话说,就是多个产品线须要绑定在一块儿,造成一个抽象的综合工厂,由具体的综合工厂来批量实现“工厂方法”的一种更“高级”的模式。
有点绕,说完这些感受我已经中文十级了。总之,我想表达的观点是:这些工厂模式并非割裂的存在,而是一个递进的思想。
建造者模式就像你委托一个室内设计师装修你的新家
官方定义
将一个复杂的构建与其表示相分离,使得一样的构建过程能够建立不一样的表示。
若是说以前的谈到的工厂模式是把建立产品(对象)的工做抽离出去的话,此次要聊的建造者模式,就是把产品内部的组件生产工做抽离出去。这样作的场景,适用于那些有着复杂、规则模块的对象生成流程。换句话说,工厂模式是一个类(工厂)建立另外一个类(产品),而建造者是一个类(产品)自身的属性(组件)构造过程。
对于建造者模式的实现网上也是版本不一:复杂点的版本会引入一个Director的角色,作一个总体上下文,组装更傻瓜化的builder和product。或是抽象一层Builder协议,用不一样的具体Builder来构造不一样的产品。但我认为这些都模糊了这个模式要传达的焦点,对理解没有帮助,因此这里我选择一个极简的模型。
struct Builder {
var partA: String
var partB: String
}
struct Product {
var partA: String
var partB: String
init(builder: Builder) {
partA = builder.partA
partB = builder.partB
}
}
// 经过builder完成产品建立工做
let b = Builder(partA: "A", partB: "B")
// 这样产品只须要一个builder就能够完成制做
let p = Product(builder: b)
复制代码
咱们让Product的生成由本身发起,可是它的组件(属性)全都委托给Builder来实现,而它只须要依赖一个Builder就完成了自身的生产工做。
原型模式让你有了一个能够源源不断自我赋值的类。
官方定义
用原型实例指定建立对象的种类,而且经过拷贝这些原型建立新的对象。
原型模式很简单,你只要实现一个返回你本身的新对象的方法便可。这里我采用的实现还不是最简单的,这个interface并非必须的。
原型模式实现了深拷贝。
protocol Prototype {
func clone() -> Prototype
}
struct Product: Prototype {
var title: String
func clone() -> Prototype {
return Product(title: title)
}
}
let p1 = Product(title: "p1")
let p2 = p1.clone()
(p2 as? Product)?.title // OUTPUT: p1
复制代码
单例就像一个公司的IT部门,他们是惟一的存在,而且被全部人直接访问。
官方定义
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
由于第二点经常被忽视,因此过分使用的危害极大,你无从知道调用从何而来,这种goto
通常的存在会变成维护的噩梦。
单例比较常见的应用是例如数据库,网络框架的全局访问点。
单例其实就是变种的原型模式,只不过原型每次返回的是一个拷贝。
Swift的简单实现:
class Singleton {
static let sharedInstance = Singleton()
private init() {
// 用private防止被new
}
}
let s = Singleton.sharedInstance
let s2 = Singleton() // ERROR: initializer is inaccessible due to 'private' protection level
复制代码
Swift的完整实现:
class Singleton {
static var singleton: Singleton? = nil
private init() {}
static func sharedInstance() -> Singleton {
if singleton == nil {
singleton = Singleton()
}
return singleton!
}
}
let s = Singleton.sharedInstance()
let s2 = Singleton() // ERROR: initializer is inaccessible due to 'private' protection level
复制代码
适配器就像一个电源转换插头。
官方定义
将一个类的接口转换成客户但愿的另一个接口。适配器模式使得本来因为接口不兼容而不能一块儿工做的那些类能够一块儿工做。
一个类能称之为适配器,就是指它能把另外一个类进行某种变形,让其能和实际需求对接。 好比一个底层数据模型,能够经过不一样的UI适配器,对应不一样的展示须要。
protocol Target {
var value: String { get }
}
struct Adapter: Target {
let adaptee: Adaptee
var value: String {
return "\(adaptee.value)"
}
init(_ adaptee: Adaptee) {
self.adaptee = adaptee
}
}
struct Adaptee {
var value: Int
}
Adapter(Adaptee(value: 1)).value // "1"
复制代码
桥接模式就是这么一座桥,它矗立在具体和抽象之间,当你调用的时候只看到了抽象,可是它内部实现时“桥接”到了具体。
官方定义
将抽象部分与实现部分分离,使它们均可以独立的变化。
桥接模式是继工厂模式以后另外一个有点绕的概念,为了避免一言不合就抛概念把你们绕晕,咱们直接来看一下这个模式最终成型的样子。
首先假设咱们封装了一个“开关能力”的接口。
protocol 开关能力 {
func turnOn(_ on: Bool)
}
复制代码
这个接口只有一个方法,实现者须要提供打开/关闭开关会触发什么操做。
而后咱们抽象一个叫“设备”的类出来,这个类就有意思了:
开关能力
的实例变量,并经过初始化赋好值。开关能力
里的实现。class 设备 {
let obj: 开关能力
func turnOn(_ on: Bool) {
obj.turnOn(on)
}
init(_ obj: 开关能力) {
self.obj = obj
}
}
复制代码
这样,其实一个桥接模式就已经搭建完了。再讲解以前,咱们直接看一下如何应用它:
class 电视: 开关能力 {
func turnOn(_ on: Bool) {
if on {
// 打开电视
} else {
// 关闭电视
}
}
}
class 空调: 开关能力 {
func turnOn(_ on: Bool) {
if on {
// 打开空调
} else {
// 关闭空调
}
}
}
let tv = 设备(电视())
tv.turnOn(true) // 打开电视
let aircon = 设备(空调())
aircon.turnOn(false) // 关闭空调
复制代码
经过这段代码能够看出:
设备
应用到具体的业务的时候,这个模式采用的是组合了一个实现了开关能力
接口的实例,而没用继承。设备
做为接入点的,而不是电视
,空调
这些具体类,具体的实现是经过组合的方式注入到设备
里的。了解了这个流程后,一个事情就明朗了:
这不就是在用组合代替继承吗?
没错,它把须要变化的代码经过接口代理出去,而避免了继承。
可是,桥接模式的桥在哪里?
这时,要搬出他的概念了: Bridge pattern - Wikipedia:
The bridge pattern is a design pattern used in software engineering which is meant to "decouple an abstraction from its implementation so that the two can vary independently". 桥接模式解耦了抽象和具体,让它们能够独立变化。
从代码去寻找具体和抽象,就能够发现:
这座桥能走通的关键就是这个组合在抽象类里的变量。
最后,你会发现,这不就是Adapter和Adaptee吗? 没错,设计模式实际上是连续剧来着。
不一样在于适配器的关注点是如何让两个不兼容的类对接, 而桥接模式关注点是解耦。
组合模式就像一个公司的组织架构,存在基层员工(Leaf)和管理者(Composite),他们都实现了组件(Component)的work方法,这种树状结构的每一级都是一个功能完备的个体。
官方定义
将对象组合成树形结构以表示"部分-总体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具备一致性。
这个组合模式不是“组合优于继承”的那种“组合”,这里是狭义的指代一种特定场景,就是如配图描述的一种树状结构。
理解这个模式要知道三个设定:
protocol Component {
func someMethod()
}
class Leaf: Component {
func someMethod() {
// Leaf
}
}
class Composite: Component {
var components = [Component]()
func someMethod() {
// Composite
}
}
let leaf = Leaf()
let composite = Composite()
composite.components += [leaf]
composite.someMethod()
leaf.someMethod()
复制代码
这个模式的精髓就是Composite
这个角色,事实上Leaf
能够看作一个特殊的Compostie
。因为他便可以是一个功能执行者,又能够包含其它节点,这个特性能够派生出泛用度很高的树状结构。
若是点咖啡时价格的计算是
牛奶(糖(咖啡(价格: 19元)))
就行了
官方定义
动态地给一个对象添加一些额外的职责。就增长功能来讲,装饰器模式相比生成子类更为灵活。
下面咱们就用装饰者模式的思想来设计这个点咖啡程序:
protocol Component {
var cost: Int { get }
}
protocol Decorator: Component {
var component: Component { get }
init(_ component: Component)
}
struct Coffee: Component {
var cost: Int
}
struct Sugar: Decorator {
var cost: Int {
return component.cost + 1
}
var component: Component
init(_ component: Component) {
self.component = component
}
}
struct Milk: Decorator {
var cost: Int {
return component.cost + 2
}
var component: Component
init(_ component: Component) {
self.component = component
}
}
Milk(Sugar(Coffee(cost: 19))).cost
复制代码
当你的需求是零散的不断给“主菜加点佐料”的时候,而且这些佐料会常常变化,那么这个模式就能够有效的解决排列组合产生的类爆炸
理解的一个关键点就是区分组件Component和装饰者Decorator两个角色,单纯组件的实现者(咖啡)是被装饰的对象,他再也不能装饰别人。
这个模式没有一个集中的计算器,每个装饰者都参与了部分计算并输出当下的结果。
外观模式就是化繁为简。
官方定义
为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
值得一提的是,这个单词读音很怪。
在谈起外观模式的时候,经常是指对一个复杂的(旧)代码库在不改变其内在的状况下,包装一层易于调用的表层API。
外观模式不会采用继承,而是用接口和组合。
protocol Facade {
func simpleMethod()
}
class LegacyCode {
func someMethod1() { }
func someMethod2() { }
}
extension LegacyCode: Facade {
func simpleMethod() {
someMethod1()
someMethod2()
}
}
class Client {
let f: Facade = LegacyCode()
}
let c = Client()
c.f.simpleMethod()
复制代码
享元模式就像CPU的Cache Memory,它经过对缓存的命中来实现速度的提高和内存的下降。
官方定义
运用共享技术有效地支持大量细粒度的对象。
享元模式其实就是指一套缓存系统。 显然他是一种复合模式,使用工厂模式来创造实例。 适用场景是系统中存在重复的对象建立过程。 好处是节省了内存加快了速度。
struct TargetObject {
var title: String?
func printTitle() {
print(title)
}
}
class Cache {
var targetObjects = [String: TargetObject]()
func lookup(key: String) -> TargetObject {
if targetObjects.index(forKey: key) == nil {
return TargetObject()
}
return targetObjects[key]!
}
}
let c = Cache()
c.targetObjects["Test"] = TargetObject(title: "Test")
c.lookup(key: "123").printTitle() // nil
c.lookup(key: "Test").printTitle() // Test
复制代码
代理模式让一个类成为了另外一个类的实际接口。
官方定义
为其余对象提供一种代理以控制对这个对象的访问。
有两种常见的代理场景:
但他们的实现是相似的:
protocol Subject {
mutating func operation()
}
struct SecretObject: Subject {
func operation() {
// real implementation
}
}
struct PublicObject: Subject {
private lazy var s = SecretObject()
mutating func operation() {
s.operation()
}
}
var p = PublicObject()
p.operation()
复制代码
SecretObject
既能够看作一个隐藏类,又能够看作一个高费的类。经过PublicObject
对他的代理,能够实现信息隐藏和延迟加载两个特性。
责任链模式就像去取钱,每一面值都参与了计算和职责传递。
官方定义
避免请求发送者与接收者耦合在一块儿,让多个对象都有可能接收请求,将这些对象链接成一条链,而且沿着这条链传递请求,直到有对象处理它为止。
UIKit的touch事件就应用了责任链模式
这个模式的实现就是既实现一个接口,又组合这个接口,这样本身执行完毕后就能够调用下一个执行者。
protocol ChainTouchable {
var next: ChainTouchable? { get }
func touch()
}
class ViewA: ChainTouchable {
var next: ChainTouchable? = ViewB()
func touch() {
next?.touch()
}
}
class ViewB: ChainTouchable {
var next: ChainTouchable? = ViewC()
func touch() {
next?.touch()
}
}
class ViewC: ChainTouchable {
var next: ChainTouchable? = nil
func touch() {
print("C")
}
}
let a = ViewA()
a.touch() // OUTPUT: C
复制代码
命令模式就是一种指令系统。
官方定义
将一个请求封装成一个对象,从而使您能够用不一样的请求对客户进行参数化。
命令模式有两个特征:
protocol Command {
var operation: () -> Void { get }
var backup: String { get }
func undo()
}
struct ConcreteCommand: Command {
var backup: String
var operation: () -> Void
func undo() {
print(backup)
}
}
struct Invoker {
var command: Command
func execute() {
command.operation()
}
func undo() {
command.undo()
}
}
let printA = ConcreteCommand(backup: "Default A") {
print("A")
}
let i1 = Invoker(command: printA)
i1.execute() // OUTPUT: A
let printB = ConcreteCommand(backup: "Default B") {
print("B")
}
let i2 = Invoker(command: printB)
i2.execute() // OUTPUT: B
i2.undo() // OUTPUT: Default B
复制代码
有了解释器,Skyrim的龙语也不在话下。
官方定义
给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。
咱们能够实现一个能计算文本“1加1”的解释器。
protocol Expression {
func evaluate(_ context: String) -> Int
}
struct MyAdditionExpression: Expression {
func evaluate(_ context: String) -> Int {
return context.components(separatedBy: "加")
.flatMap { Int($0) }
.reduce(0, +)
}
}
let c = MyAdditionExpression()
c.evaluate("1加1") // OUTPUT: 2
复制代码
迭代器就像回转寿司,保证每一道菜品都能获得展现。
官方定义
提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。
迭代器就是能用hasNext和Next来遍历的一种集合元素。
他很像责任链,可是责任链是一个随时可断的链条(有可能在某一个节点再也不把责任下放),他不强调一次完整的遍历。迭代器更像一次次的循环,每次循环都强调完整性,因此更适合集合的场景。
还有一个区别是迭代器是提供元素,而责任链在每个经手人那作业务。
protocol AbstractIterator {
func hasNext() -> Bool
func next() -> Int?
}
class ConcreteIterator: AbstractIterator {
private var currentIndex = 0
var elements = [Int]()
func next() -> Int? {
guard currentIndex < elements.count else { currentIndex = 0; return nil }
defer { currentIndex += 1 }
return elements[currentIndex]
}
func hasNext() -> Bool {
guard currentIndex < elements.count else { currentIndex = 0; return false }
return elements[currentIndex] != nil
}
}
protocol AbstractCollection {
func makeIterator() -> AbstractIterator
}
class ConcreteCollection: AbstractCollection {
let iterator = ConcreteIterator()
func add(_ e: Int) {
iterator.elements.append(e)
}
func makeIterator() -> AbstractIterator {
return iterator
}
}
let c = ConcreteCollection()
c.add(1)
c.add(2)
c.add(3)
let iterator = c.makeIterator()
while iterator.hasNext() {
print(iterator.next() as Any)
}
复制代码
中介模式是对象间发消息的传话筒。
官方定义
用一个中介对象来封装一系列的对象交互,中介者使各对象不须要显式地相互引用,从而使其耦合松散,并且能够独立地改变它们之间的交互。
中介模式对于通讯关系复杂的系统有很好的解耦效果
它和观察者模式很像,区别在于观察者是不关心接受方的广播,中介者是介入两个(或多个)对象之间的定点消息传递。
protocol Receiver {
func receive(message: String)
}
protocol Mediator: class {
func notify(message: String)
func addReceiver(_ receiver: Receiver)
}
class ConcreteMediator: Mediator {
var recipients = [Receiver]()
func notify(message: String) {
recipients.forEach { $0.receive(message: message) }
}
func addReceiver(_ receiver: Receiver) {
recipients.append(receiver)
}
}
protocol Component: Receiver {
var mediator: Mediator? { get }
}
struct ConcreteComponent: Component {
weak var mediator: Mediator?
var name: String
func receive(message: String) {
print(name, " receive: ", message)
}
}
var mediator = ConcreteMediator()
let c1 = ConcreteComponent(mediator: mediator, name: "c1")
let c2 = ConcreteComponent(mediator: mediator, name: "c2")
let c3 = ConcreteComponent(mediator: mediator, name: "c3")
mediator.addReceiver(c1)
mediator.addReceiver(c2)
mediator.addReceiver(c3)
//c1 receive: hi
//c2 receive: hi
//c3 receive: hi
c1.mediator?.notify(message: "hi")
复制代码
备忘录就是游戏存档。
官方定义
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象以外保存这个状态。
这个模式有两个角色,一个是要存储的类型自己(Memento)和执行存储操做的保管人(Caretaker)。
typealias Memento = [String: String] // chatper: level
protocol MementoConvertible {
var memento: Memento { get }
init?(memento: Memento)
}
class GameState: MementoConvertible {
var memento: Memento {
return [chapter: level]
}
var chapter: String
var level: String
required init?(memento: Memento) {
self.chapter = memento.keys.first ?? ""
self.level = memento.values.first ?? ""
}
init(chapter: String, level: String) {
self.chapter = chapter
self.level = level
}
}
protocol CaretakerConvertible {
static func save(memonto: Memento, for key: String)
static func loadMemonto(for key: String) -> Memento?
}
class Caretaker: CaretakerConvertible {
static func save(memonto: Memento, for key: String) {
let defaults = UserDefaults.standard
defaults.set(memonto, forKey: key)
defaults.synchronize()
}
static func loadMemonto(for key: String) -> Memento? {
let defaults = UserDefaults.standard
return defaults.object(forKey: key) as? Memento
}
}
let g = GameState(chapter: "Prologue", level: "0")
// after a while
g.chapter = "Second"
g.level = "20"
// want a break
Caretaker.save(memonto: g.memento, for: "gamename")
// load game
let gameState = Caretaker.loadMemonto(for: "gamename") // ["Second": "20"]
复制代码
观察者模式就像预购,支付了就坐等收货,省心省力。
官方定义
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,全部依赖于它的对象都获得通知并被自动更新。
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的全部依赖者都会收到通知并自动更新。
观察者模式适用在一个被观察者(数据源)要通知多个观察者的场景。
这个模式主要经过添加一层接口,来把观察者的具体类型擦除,从何实现松耦合。(针对接口编程,而非实现)
观察者模式一个最重要的特色就是它是一种推模型(PUSH),在不少状况下比拉模型更高效和即时。
protocol Observable {
var observers: [Observer] { get }
func add(observer: Observer)
func remove(observer: Observer)
func notifyObservers()
}
class ConcreteObservable: Observable {
var observers = [Observer]()
func add(observer: Observer) {
observers.append(observer)
}
func remove(observer: Observer) {
if let index = observers.index(where: { $0 === observer }) {
observers.remove(at: index)
}
}
func notifyObservers() {
observers.forEach { $0.update() }
}
}
protocol Observer: class {
func update()
}
class ConcreteObserverA: Observer {
func update() { print("A") }
}
class ConcreteObserverB: Observer {
func update() { print("B") }
}
//////////////////////////////////
let observable = ConcreteObservable()
let a = ConcreteObserverA()
let b = ConcreteObserverB()
observable.add(observer: a)
observable.add(observer: b)
observable.notifyObservers() // output: A B
observable.remove(observer: b)
observable.notifyObservers() // output: A
复制代码
状态模式就像马里奥吃了各类蘑菇后的变身。
官方定义
容许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
状态模式很像策略模式,可是他封装的不是算法,而是一个一个本体下的不一样状态。
protocol State {
func operation()
}
class ConcreteStateA: State {
func operation() {
print("A")
}
}
class ConcreteStateB: State {
func operation() {
print("B")
}
}
class Context {
var state: State = ConcreteStateA()
func someMethod() {
state.operation()
}
}
let c = Context()
c.someMethod() // OUTPUT: A
c.state = ConcreteStateB() // switch state
c.someMethod() // OUTPUT: B
复制代码
策略模式就像不一样的交通路线,能够相互替换,都能达到终点。
官方定义
定义一系列的算法,把它们一个个封装起来, 而且使它们可相互替换。
这个模式主要仍是在解决一个稳定的继承树当遇到需求变化时的狼狈。他把变化的几种状况分门别类的封装好,而后把本身变化的部分隔离成一个接口实例(interface/protocol),让子类继承后来作选择题。
一个须要转变的思惟就是:建模一个类的时候不能只局限于物体(object),也能够是行为(behavior)的封装。
用继承来替换interface实现多态的behavior也是理论可行的。 这个模式还有一个强大之处:他是动态的。就是至于子类选择了什么行为,不见得是写代码的时候写死的;也能够是相似用户点击一下切换了算法。
protocol WeaponBehavior {
func use()
}
class SwordBehavior: WeaponBehavior {
func use() { print("sword") }
}
class BowBehavior: WeaponBehavior {
func use() { print("bow") }
}
class Character {
var weapon: WeaponBehavior?
func attack() { weapon?.use() }
}
class Knight: Character {
override init() {
super.init()
weapon = SwordBehavior()
}
}
class Archer: Character {
override init() {
super.init()
weapon = BowBehavior()
}
}
///////////////////////////////////
Knight().attack() // output: sword
Archer().attack() // output: bow
复制代码
模板方法就是抽象类衍生出来的继承树。
官方定义
定义一个操做中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类能够不改变一个算法的结构便可重定义该算法的某些特定步骤。
模板方法指的就是存在在抽象类中的方法声明,由子类具体实现。这样这套方法产生了一套模具不一样产品的派生效果。
经过抽象类和子类实现多态。
很惋惜Swift/Objective-C都没有抽象类, 因此用通常的class来实现
class Soldier {
func attack() {} // <-- Template Method
private init() {} // <-- avoid creation
}
class Paladin: Soldier {
override func attack() {
print("hammer")
}
}
class Archer: Soldier {
override func attack() {
print("bow")
}
}
复制代码
访问者模式就像一个个酒店,他们都有接待你的接口,你入住后又会利用它的基础设施。
官方定义
主要将数据结构与数据操做分离。
号称最复杂的设计模式,先看一下实现代码。
protocol Visitor {
func visit(_ c: ComponentA)
func visit(_ c: ComponentB)
}
struct ConcreteVisitor: Visitor {
func visit(_ c: ComponentA) {
c.featureA()
}
func visit(_ c: ComponentB) {
c.featureB()
}
}
protocol Component {
func accept(_ v: Visitor)
}
struct ComponentA: Component {
func featureA() {
print("Feature A")
}
func accept(_ v: Visitor) {
v.visit(self)
}
}
struct ComponentB: Component {
func featureB() {
print("Feature B")
}
func accept(_ v: Visitor) {
v.visit(self)
}
}
let components: [Component] = [ComponentA(), ComponentB()]
components.forEach {
$0.accept(ConcreteVisitor())
}
复制代码
要理解这个模式须要了解一些概念:
经过这篇文章能够了解到, 其实设计模式(GoF)中的Visitor模式就是Java解决Double Dispatch的一种应用。,
咱们用Swift实现一下文中双重派发问题
protocol Event {}
class BlueEvent: Event {}
class RedEvent: Event {}
class Handler {
func handle(_ e: Event) {
print("Event")
}
func handle(_ r: RedEvent) {
print("RedEvent")
}
func handle(_ b: BlueEvent) {
print("BlueEvent")
}
}
let b = BlueEvent()
let r = RedEvent()
let c = Handler()
c.handle(b) // OUTPUT: BlueEvent
c.handle(r) // OUTPUT: RedEvent
复制代码
验证发现,Swift是支持双重派发的。
那什么是双重派发(Double Dispatch)?
要解释这个,要先搞清楚什么是派发(Dispatch)。
派发或者说Single Dispatch,Dynamic Dispatch,wiki是这样解释的:
In most object-oriented systems, the concrete function that is called from a function call in the code depends on the dynamic type of a single object and therefore they are known as single dispatch calls, or simply virtual function calls. 抓要点说,就是多态的状况下,在运行时,虚方法是由哪一个具体实现执行了,这样的绑定动做就是派发。
那什么虚方法? 看一下wiki的解释:
In short, a virtual function defines a target function to be executed, but the target might not be known at compile time. 虚方法就是编译期还没肯定实现的方法,咱们能够理解成接口中声明的方法。
好,咱们如今知道了一次虚方法的绑定就是派发,双重派发就是两次绑定操做。
那双重派发的代码是如何触发派发的呢?
理解这些就知道这个模式在折腾什么了。
固然他也有另外的好处,就是可让具体的业务类能够丢到集合里批量的执行accept visitor。