diff --git "a/Design Pattern/\347\256\200\345\215\225\345\267\245\345\216\202\343\200\201\345\267\245\345\216\202\346\226\271\346\263\225\343\200\201\346\212\275\350\261\241\345\267\245\345\216\202.md" "b/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\345\210\233\345\273\272\345\236\213/\347\256\200\345\215\225\345\267\245\345\216\202\343\200\201\345\267\245\345\216\202\346\226\271\346\263\225\343\200\201\346\212\275\350\261\241\345\267\245\345\216\202.md" similarity index 98% rename from "Design Pattern/\347\256\200\345\215\225\345\267\245\345\216\202\343\200\201\345\267\245\345\216\202\346\226\271\346\263\225\343\200\201\346\212\275\350\261\241\345\267\245\345\216\202.md" rename to "docs/\350\256\276\350\256\241\346\250\241\345\274\217/\345\210\233\345\273\272\345\236\213/\347\256\200\345\215\225\345\267\245\345\216\202\343\200\201\345\267\245\345\216\202\346\226\271\346\263\225\343\200\201\346\212\275\350\261\241\345\267\245\345\216\202.md" index bcf8212..775583b 100644 --- "a/Design Pattern/\347\256\200\345\215\225\345\267\245\345\216\202\343\200\201\345\267\245\345\216\202\346\226\271\346\263\225\343\200\201\346\212\275\350\261\241\345\267\245\345\216\202.md" +++ "b/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\345\210\233\345\273\272\345\236\213/\347\256\200\345\215\225\345\267\245\345\216\202\343\200\201\345\267\245\345\216\202\346\226\271\346\263\225\343\200\201\346\212\275\350\261\241\345\267\245\345\216\202.md" @@ -1,11 +1,9 @@ -# 简单工厂、工厂方法、抽象工厂 +# 简单工厂、工厂方法、抽象工厂 -标签(空格分隔): 设计模式 Swift +Swift --- -[TOC] - **简单工厂**不是一个设计模式,而是一直编码习惯 **工厂方法**是定义一个创建对象的接口,子类实现该接口,子类决定具体创建哪个对象。 diff --git "a/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\345\221\275\344\273\244\346\250\241\345\274\217.md" "b/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\345\221\275\344\273\244\346\250\241\345\274\217.md" deleted file mode 100644 index e62bdd8..0000000 --- "a/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\345\221\275\344\273\244\346\250\241\345\274\217.md" +++ /dev/null @@ -1,565 +0,0 @@ -# 命令模式 - -标签(空格分隔): 设计模式 Swift - ---- - -[TOC] - -**命令行模式是将请求(或称为方法调用)封装成对象**,让动作的请求者与动作执行者解耦,动作的请求者无需知道具体的动作执行者,动作请求者只要知道如何发起请求即可。 - -命令行模式涉及如下对象: - -+ **Command**:声明执行操作的**接口**; -+ **ConcreateCommand**: 具体的 Command,这里需要将动作接受者(Receiver)绑定到该对象中,并实现 Command 接口定义的 `excute` 方法; -+ **Receiver**:动作最终执行者; -+ **Invoker**:执行命令的的对象或者动作; -+ **Client**:创建命令对象(Command)并且设定命令对象的接收者(Receiver),并将 Invoker 与命令关联起来。 - -PS: 有些教程不会提到 Client 对象,但并不妨碍我们理解命令模式。 - -## 案例一:餐厅顾客点餐 - -> 这个案例来自于 《Head First 设计模式》 第六章,主要目的帮助理解书中各种对象。 - - 顾客准备在餐馆用餐,顾客点好菜( CreateOrder )后交给服务员,服务员交给(takeCommand)厨房,并通知(orderUp),厨房的厨师(Cook)按照客户下的订单进行烹饪。 - -``` -class Cook { - func makeCake() { - debugPrint( "make cake" ) - } - - func makeTurkey() { - debugPrint( "make turkey" ) - } -} - -protocol Command { - func excute() -} - -class CakeCommand: Command { - let cook: Cook - - init(cook: Cook) { - self.cook = cook - } - - func excute() { - cook.makeCake() - } -} - -class TurkeyCommand: Command { - let cook: Cook - init(cook: Cook) { - self.cook = cook - } - - func excute() { - cook.makeTurkey() - } -} - -/// client -class Customer { - func createOrder() { - - /// Invoker - let waitress = Waitress() - - // Receiver - let cook = Cook() - - /// Command: CakeCommand - /// Receiver Cook - let cakeCommand = CakeCommand(cook: cook) - - /// Command: TurkeyCommand - /// Receiver: cook - let turkeyCommand = TurkeyCommand(cook: cook) - - - /// setCommand - waitress.takeCommand(command: cakeCommand) - waitress.takeCommand(command: turkeyCommand) - - /// trigger - waitress.orderUp() - } -} - -class Waitress { - - var commands: [Command] = [] - - func takeCommand(command: Command) { - self.commands.append(command) - } - - func orderUp() { - self.commands.forEach( { $0.excute() } ) - } -} -``` - -在上面例子中,`Customer` 创建了命令(`CakeCommand`、`TurkeyCommand`),并将命令最终的执行者(`Cook`)绑定到命令中,再通过调用 `takeCommand` 将命令绑定到 `Invoker(Waitress)` 中。当 `Invoker(Waitress)` 触发 `orderUp` 后,让 `commands` 依次执行 `excute` 方法,最终使得 命令最终的执行者(`Cook`)去执行响应的动作(`cook.makeCake()` 和 `cook.makeTurkey()`)。 - -通过上面的例子,你应该大概了解命令模式的大体流程:**将方法封装成对象,解耦命令最终执行者与命令请求者**。但上面的例子可能会给你造成一定的困惑,因为上面的最终执行者(Cook)是明确的,看起来好像是为了使用设计模式而使用设计模式。接下里请看案例二 - -## 案例二:遥控器插槽案例 - -> 这个案例来自于 《Head First 设计模式》第六章命令模式 - -![](https://tva1.sinaimg.cn/large/e6c9d24egy1h4tztgkn43j20zj0u0dis.jpg) - -**需求:**现在有个遥控器有 7 个插槽,每个插槽上可以控制打开(on) 和关闭(off)两种状态,插槽的底部还有个(undo)状态,可以一键 undo 所有的插槽上的设备。 - -**分析:**遥控器不知道插槽上最终是哪个来执行,这只一种典型命令请求者(遥控器)与最终执行者(插槽上的设备)分离的状态。 - -### 简化处理:一个插槽 -这一篇,我们讨论的是命令模式,这里为了简化处理,先处理一个插槽情况 - -``` -class Light { - func on() { debugPrint( "Light on") } - func off() { debugPrint( "Light off") } -} - -protocol Command { - func excute() -} - -class LightOnCommand: Command { - let light: Light - - init(light: Light) { self.light = light } - - func excute() { self.light.on() } -} - -class LightOffCommand: Command { - let light: Light - - init(light: Light) { self.light = light } - - func excute() { self.light.off() } -} - - -class SimpleRemoteControl { - var slot: Command? - - init() {} - - func setCommand(command: Command) { - self.slot = command - } - - func buttonWasPressed() { - self.slot?.excute() - } -} - -``` - -调用地方: -``` -/// Receiver -let light = Light() - -/// Command -let lightOnCommand = LightOnCommand(light: light) - -/// Invoker -let simpleRemoteControl = SimpleRemoteControl() - -/// 讲 Invoker 与 Command 关联起来 -simpleRemoteControl.setCommand(command: lightOnCommand) - -/// Invoker触发事件 -simpleRemoteControl.buttonWasPressed() -``` - -在上面例子中,我们封装了 `LightOnCommand` 的命令,然后 Invoker 在触发事件的时候,只要调用命令的 `excute()` 方法,而不用最终关心是谁进行最终的执行(这里最终是 `Light.on()`),从而达到最终执行者与请求者分离的情况。 - -### 七个插槽 - -上面的一个插槽简化处理还不错,这里需要考虑所有的插槽,其实很简单,遥控器存储七对命令(on 和 off 为一对),当按钮按 on 或者 off 时,调用对应插槽命令的 `excute` 即可,因为命令中保存着最终的执行者。 - -``` -class NoCommand: Command { - func excute() { } -} - -class SimpleRemoteControl { - - var onCommands: [Command] = Array(repeating: NoCommand(), count: 7) - var offCommands: [Command] = Array(repeating: NoCommand(), count: 7) - - func setCommand(slot: Int, onCommand: Command, offCommand: Command) { - self.onCommands[slot] = onCommand - self.offCommands[slot] = offCommand - } - - func onButtonWasPressed(at slot: Int) { - self.onCommands[slot].excute() - } - - func offButtonWasPressed(at slot: Int) { - self.offCommands[slot].excute() - } -} - -``` -上面的实例中,`onCommands` 与 `offCommands` 默认使用 `NoCommand` 进行初始化,这是为了在调用的时候重复写判空代码。 - -### Undo 撤销操作 - -进行 Undo 的时候,需要考虑 Undo 命令放在哪里? -#### Undo - SimpleRemoteControl 中简单取反处理 -第一反应是将 Undo 放到 `SimpleRemoteControl` 中,点击 `onButtonWasPressed` 时候,记下 `offCommand`, 点击 `offButtonWasPressed` 时候,记下 `onCommand`: - -``` -class SimpleRemoteControl { - - var undoCommand: Command? - - func onButtonWasPressed(at slot: Int) { - self.onCommands[slot].excute() - - /// On 记下 Off 状态 - self.undoCommand = self.offCommands[slot] - } - - func offButtonWasPressed(at slot: Int) { - self.offCommands[slot].excute() - - /// Off 记下 On 状态 - self.undoCommand = self.onCommands[slot] - } - - func undoButtonWasPressed() { - self.undoCommand?.excute() - } -} -``` -分析上面的代码,这里存在两个问题: - -1. 实际项目中,可能并不是 on/off 两种状态,如果有第三种状态,那么就无法设置 undo 状态; -2. 无法处理连续的 Undo 操作。 - -针对第一个问题: 假设风扇风速有三种状态: 高、中、低,那么在进行 Undo 的时候,需要记录上次的状态。 Command 模式中解耦了 **Invoker**(SimpleRemoteControl) 以及最终执行者(CeilingFan),因此无法在 SimpleRemoteControl 中获取 CeilingFan 状态。 - -第二个问题更加明显,on 的时候,undoCommand 为 off, 当多次执行的时候,还是执行 offCommand, 这显然不合适。 - -#### Undo - Command 协议中定义 - -为了处理上面第一个问题,我们需要将 Undo 操作设置在 Command 中(因为只有 Command 能获取到 Receiver 状态)。拿上面风扇的例子来说,可以如下设置 - -```swift -class CeilingFan { - enum Speed { - case off - case high - case medium - case low - } - - var speed: Speed = .off -} - -class CeilingFanHighCommand: Command { - let ceilingFan: CeilingFan - - var previewSpeed: CeilingFan.Speed = .off - - init(ceilingFan: CeilingFan) { - self.ceilingFan = ceilingFan - } - - func excute() { - self.previewSpeed = self.ceilingFan.speed - - self.ceilingFan.speed = .high - - debugPrint( "CeilingFanHighCommand, preview speed is \(self.previewSpeed)" ) - } - - func undo() { - let speed = self.ceilingFan.speed - self.ceilingFan.speed = previewSpeed - self.previewSpeed = speed - - debugPrint( "CeilingFanHighCommand, undo to \(self.previewSpeed)" ) - } -} - -class CeilingFanMediumCommand: Command { } -class CeilingFanOffCommand: Command { } -class CeilingFanLowCommand: Command { } - -``` -在 Ceiling 的命令中,每次执行 `excute` 前会记录 CeilingFan 上次的状态,在执行 undo 的时候,将状态置回去。 - -调用 - -```swift - -let ceilingFan = CeilingFan() - -let high = CeilingFanHighCommand(ceilingFan: ceilingFan) -let medium = CeilingFanMediumCommand(ceilingFan: ceilingFan) -let low = CeilingFanLowCommand(ceilingFan: ceilingFan) -let off = CeilingFanOffCommand(ceilingFan: ceilingFan) - -let simpleRemoteControl = SimpleRemoteControl() -simpleRemoteControl.setCommand(slot: 0, onCommand: high, offCommand: off) -simpleRemoteControl.setCommand(slot: 1, onCommand: medium, offCommand: off) -simpleRemoteControl.setCommand(slot: 2, onCommand: low, offCommand: off) - -simpleRemoteControl.onButtonWasPressed(at: 0) -simpleRemoteControl.undoButtonWasPressed() - -simpleRemoteControl.onButtonWasPressed(at: 0) -simpleRemoteControl.offButtonWasPressed(at: 0) -simpleRemoteControl.undoButtonWasPressed() -simpleRemoteControl.undoButtonWasPressed() - -``` - -这个实例中,解决了上面的第一个 undo 状态问题,针对于第二个问题,也解决了部分。现在状态可以来回切换了 - -``` -"CeilingFanHighCommand, preview speed is off" -"CeilingFanHighCommand, undo to high" -"CeilingFanHighCommand, undo to off" -"CeilingFanHighCommand, undo to high" -"CeilingFanHighCommand, undo to off" -"CeilingFanHighCommand, undo to high" -"CeilingFanHighCommand, undo to off" -"CeilingFanHighCommand, undo to high" -``` - -显然上面的 Undo 显然与部分人设想的不一样,有时候希望 Undo 一直 undo 到最开始状态,而不仅仅在两个状态中切换,如果你需要这样,你可能最终设置栈来操作了,这不是本篇的内容了。 - - -### 延伸 - 宏命令 - -在命令模式中,将 Receiver 绑定到命令中,然后执行 `excute` 或者 `undo`, 如果 Command 中存在多个Receiver,然后执行 `excute` 或 `undo` 时,遍历 Receiver 依次执行,这就是宏命令。 - -例如:一键开启家庭的电视、音响、热水器、空调等等 - - -## 延伸-非要用 Command 模式吗? - -本文讲述的是 Command 命令行模式,已经可以结束了。但是对上面的案例来说,有没有比它更好的方案来实现呢? 现在来探索下。 - -设想1:插槽上的设备有两种状态,on/off, 是否可以定义设置的接口 - -``` - -protocol SlotDevice { - func on() - func off() -} - -class Light: SlotDevice { - func on() { debugPrint( "Light on") } - func off() { debugPrint( "Light off") } -} -``` -然后在调用地方 - -``` -class SimpleRemoteControl { - - var slots: [SlotDevice?] = Array(repeating: nil, count: 7) - - func setDevice(slot: Int, device: SlotDevice) { - self.slots[slot] = device - } - - func onButtonWasPressed(at slot: Int) { - self.slots[slot]?.on() - } - - func offButtonWasPressed(at slot: Int) { - self.slots[slot]?.off() - } -} - -``` -看起来简介很多,但是对于 CeilingFan 怎么设置呢? - -CeilingFan 并不是单纯的 on/off 状态,因此,我们需要使用**适配器模式**来进行适配. - -``` -class CeilingFan { - enum Speed { - case off - case high - case medium - case low - } - - var speed: Speed = .off { - didSet { - debugPrint("set speed to \(self.speed)") - } - } -} - -class CeilingFanAdapter: SlotDevice { - - let ceilingFan: CeilingFan - let onSpeed: CeilingFan.Speed - let offSpeed: CeilingFan.Speed - - init(ceilingFan: CeilingFan, onSpeed: CeilingFan.Speed, offSpeed: CeilingFan.Speed = .off) { - self.ceilingFan = ceilingFan - self.onSpeed = onSpeed - self.offSpeed = offSpeed - } - - func on() { - self.ceilingFan.speed = self.onSpeed - } - - func off() { - self.ceilingFan.speed = self.offSpeed - } -} -``` -在调用的地方: - -``` - -let light = Light() - -let ceilingFan = CeilingFan() - -let high = CeilingFanAdapter(ceilingFan: ceilingFan, onSpeed: .high, offSpeed: .off) -let medium = CeilingFanAdapter(ceilingFan: ceilingFan, onSpeed: .medium, offSpeed: .off) -let low = CeilingFanAdapter(ceilingFan: ceilingFan, onSpeed: .low, offSpeed: .off) - -let simpleRemoteControl = SimpleRemoteControl() -simpleRemoteControl.setDevice(slot: 0, device: light) -simpleRemoteControl.setDevice(slot: 1, device: high) -simpleRemoteControl.setDevice(slot: 2, device: medium) -simpleRemoteControl.setDevice(slot: 3, device: low) - -simpleRemoteControl.onButtonWasPressed(at: 0) -simpleRemoteControl.offButtonWasPressed(at: 0) - -simpleRemoteControl.onButtonWasPressed(at: 1) -simpleRemoteControl.onButtonWasPressed(at: 2) -simpleRemoteControl.onButtonWasPressed(at: 3) -``` -**但是对于 Undo 呢?** - -``` -protocol SlotDevice { - func on() - func off() - func undo() -} - -class Light: SlotDevice { - - // 记住状态 - var ison: Bool = false - - func on() { debugPrint( "Light on") } - func off() { debugPrint( "Light off") } - func undo() { - self.ison ? self.off() : self.on() - } -} - - -class CeilingFan { - enum Speed { - case off - case high - case medium - case low - } - - var speed: Speed = .off { - didSet { - debugPrint("set speed to \(self.speed)") - } - } -} - -class CeilingFanAdapter: SlotDevice { - - let ceilingFan: CeilingFan - let onSpeed: CeilingFan.Speed - let offSpeed: CeilingFan.Speed - - // 记住状态 - var previewSpeed: CeilingFan.Speed = .off - - init(ceilingFan: CeilingFan, onSpeed: CeilingFan.Speed, offSpeed: CeilingFan.Speed = .off) { - self.ceilingFan = ceilingFan - self.onSpeed = onSpeed - self.offSpeed = offSpeed - } - - func on() { - self.ceilingFan.speed = self.onSpeed - self.previewSpeed = self.ceilingFan.speed - } - - func off() { - self.ceilingFan.speed = self.offSpeed - - self.previewSpeed = self.ceilingFan.speed - } - - func undo() { - let speed = self.ceilingFan.speed - self.ceilingFan.speed = self.previewSpeed - - self.previewSpeed = speed - } -} - - - -class SimpleRemoteControl { - - var slots: [SlotDevice?] = Array(repeating: nil, count: 7) - - func setDevice(slot: Int, device: SlotDevice) { - self.slots[slot] = device - } - - func onButtonWasPressed(at slot: Int) { - self.slots[slot]?.on() - } - - func offButtonWasPressed(at slot: Int) { - self.slots[slot]?.off() - } - - func undoButtonWasPressed() { - for slot in self.self.slots { - slot?.undo() - } - } -} - -``` - -似乎也能达到上面的效果, 完了? 塌房了? - - -## 总结 - -通过上面的实例,你可以了解到 Command 模式的优缺点,优点很明显:**解耦了命令请求者与执行者。** 但是缺点也很明显:**每个动作都需要形成一个类,类型最后爆炸。** 这个缺点在很大程度上会限制在实际项目中的使用。得益于现代语言的高级特性,我们也可以考虑使用其他的方式来进行实现类似的功能。 - -尽管如此,这也不妨碍我们学习命令行模式--Over \ No newline at end of file diff --git "a/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\347\255\226\347\225\245\346\250\241\345\274\217.md" "b/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\347\255\226\347\225\245\346\250\241\345\274\217.md" deleted file mode 100644 index 6ef1cd3..0000000 --- "a/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\347\255\226\347\225\245\346\250\241\345\274\217.md" +++ /dev/null @@ -1,285 +0,0 @@ -# 策略模式 - -标签(空格分隔): 设计模式 Swift - ---- - -[TOC] - -面向对象的三大特性:封装、继承和多态,这三大特性其实为了方便代码的**复用**与**管理**。 - - -## 需求一:设计鸭子模型 - -**需求:**设计一款简单的鸭子游戏,里面鸭子可以进行嘎嘎叫(quack)以及游泳(swim),还需要一个能展示鸭子外观(display)的方法。 - -为了代码的复用(quack 以及 swim),设计一个超类 Duck,子类继承于 Duck,但是子类各自实现不同的部分(display),标准的 OO 编程。 - -```swift -class Duck { - func quack() { debugPrint("嘎嘎") } - func swim() { debugPrint("游泳") } - func display() { fatalError(" subclass implement ") } -} - -class MallarDuck: Duck { - override func display() { debugPrint( "Mallar Duck display" ) } -} - -class RedHeadDuck: Duck { - override func display() { debugPrint( "Red Head Duck display" ) } -} - -``` - -### 延伸思考:鸭子类型能否使用接口或者协议来表示? - -``` -protocol DuckRepresentable { - func quack() - func swim() - func display() -} - -extension DuckRepresentable { - func quack() { debugPrint("嘎嘎") } - func swim() { debugPrint("游泳") } -} - - -class MallarDuck: DuckRepresentable { - func display() { debugPrint( "Mallar Duck display" ) } -} - -class RedHeadDuck: DuckRepresentable { - func display() { debugPrint( "Red Head Duck display" ) } -} -``` -得益于 Swift 协议扩展默认实现,可以复用 `quack` 以及 `swim` 代码。但在其他语言中(例如 Java),就需要为每个遵循 `DuckRepresentable` 接口的类型实现 `quack`、`swim` 以及 `display` 方法,代码得不到复用。 - ---- - -## 需求二:让鸭子能飞吧 - -**需求:** 项目迭代,需要给鸭子添加飞行的能力。同时需要增加一种橡皮鸭(RubberDuck),这种鸭子发出吱吱叫声,但是不能飞。 - -需求需要鸭子会飞,因此在父类 Duck 中 `fly()` 方法即可。橡皮鸭重写 `quack()` 以及 `fly()` - -```swift -class Duck { - func quack() { debugPrint("嘎嘎") } - func swim() { debugPrint("游泳") } - func display() { fatalError(" subclass implement ") } - func fly() { debugPrint( "飞行" ) } -} - -class MallarDuck: Duck { - override func display() { debugPrint( "Mallar Duck display" ) } -} - -class RedHeadDuck: Duck { - override func display() { debugPrint( "Red Head Duck display" ) } -} - -class RubberDuck: Duck { - override func quack() { debugPrint("吱吱") } - override func display() { debugPrint( "Rubber Duck display" ) } - override func fly() { /* 不能飞行 */ } -} - -``` - -Great!完成了需求!下面来分析下这里面可能面临的问题: - -1. 每增加一种行为需要检查所有的鸭子是否符合该行为; -2. 与父类行为不一致的代码无法进行复用; -3. 无法在运行时改变对应的行为; - -采用继承的方式,都需要关注子类是否能够继承父类所有的行为,每添加一种行为,也必须检查**所有**子类是否符合这种行为,如果不符合,大多就是重写改行为(例如 fly 啥也不做)。但在理想情况下,**每一个子类对象仅仅需要关注只能能支持的行为,而不需要考虑自己不支持的行为**。当然可以让系统默认啥都不干,让子类进行关注自己的行为,但这同样引出了问题 3;如果多种鸭子有相同的行为且与父类默认行为不一致,此部分的代码就无法复用了。 -问题 4 很明显,目前的类型无法在运行时动态改变。 - -### 延伸思考1:能否将 Fly 和 Quack 变化部分接口编程 -每新增一种行为(例如 fly), 我们需要检查所有的子类是否符合该行为,那么能否用接口 flyable 接口,让拥有改行为的 Duck 遵循接口? - -``` -protocol Flyable { - func fly() -} - -protocol Quackable { - func quack(); -} - -class Duck { - func swim() { debugPrint("游泳") } - func display() { fatalError(" subclass implement ") } -} - -class MallarDuck: Duck, Flyable, Quackable { - override func display() { debugPrint( "Mallar Duck display" ) } - - func quack() { debugPrint("嘎嘎") } - func fly() { debugPrint( "Mallar Duck fly" ) } -} - -class RedHeadDuck: Duck,Flyable, Quackable { - override func display() { debugPrint( "Red Head Duck display" ) } - - func quack() { debugPrint("嘎嘎") } - func fly() { debugPrint( "Red Head Duck fly" ) } -} - -class RubberDuck: Duck, Quackable{ - override func display() { debugPrint( "Rubber Duck display" ) } - func quack() { debugPrint("吱吱") } -} - -``` -面向接口编程,虽然能解决掉问题 1, 但是问题 2 和 问题 3 依旧存在。 - -### 延伸思考2:继承 VS 组合 - -继承是在**编译期间**确定的**强耦合**的结构关系; -组合是在**运行期间**确定的**松耦合关系**,组合能够在运行时动态改变该行为。 - -同时在大多数语言中支持遵循多接口,但不支持遵循多继承(笔者目前就知道 C++支持多继承)。 - ---- - -## 整合鸭子行为 - -为了代码行为复用,我们将变化的部分(fly 以及 quack)提取出来,为了动态改变对应的行为,采用接口编程动态的改变行为。 - -``` -protocol FlyBehavior { - func fly() -} - -class FlyNoWay: FlyBehavior { - func fly() { debugPrint( "无法飞行") } -} - -class FlyWithWings: FlyBehavior { - func fly() { debugPrint( "可飞行 ") } -} - -protocol QuackBehavior { - func quack() -} - -class MuteQuack: QuackBehavior { - func quack() { debugPrint( "无法叫" ) } -} - -class Squeak: QuackBehavior { - func quack() { debugPrint( "嘎嘎" ) } -} - -class Creak: QuackBehavior { - func quack() { debugPrint( "吱吱" ) } -} - -class Duck { - - var flyBehavior: FlyBehavior? - var quackBehavior: QuackBehavior? - - func swim() { debugPrint("游泳") } - func display() { } - - func makeFly() { flyBehavior?.fly() } - func makeQuack() { quackBehavior?.quack() } -} - -class MallarDuck: Duck { - - override init() { - super.init() - - self.flyBehavior = FlyWithWings() - self.quackBehavior = Squeak() - } - - override func display() { debugPrint( "Mallar Duck display" ) } -} - -class RedHeadDuck: Duck { - - override init() { - super.init() - - self.flyBehavior = FlyWithWings() - self.quackBehavior = Squeak() - } - - override func display() { debugPrint( "Red Head Duck display" ) } -} - -class RubberDuck: Duck { - override init() { - super.init() - - self.flyBehavior = FlyNoWay() - self.quackBehavior = Creak() - } - - // 动态改变行为 - func makeWing() { - self.flyBehavior = FlyWithWings() - } - - // 动态改变行为 - func makeBroken() { - self.quackBehavior = MuteQuack() - } - - override func display() { debugPrint( "Rubber Duck display" ) } - -} - - -``` - -这里将 FlyBehavior 和 QuackBehavior 设计为接口,并未设计为一个类。这是因为接口更加灵活,设计为具体类后,以后想改变该类型需要强绑定为该类的子类,缺少灵活性。**尽量针对接口编程,而不是针对实现编程** - -采用组合的方式将 FlyBehavior 和 QuackBehavior 联系起来,在运行时动态的去确定具体的类型。 - - -## 策略模式 - -**定义:** 策略模式就是讲算法簇封装起来,然后让他们可以相互替换,并且可以再不同的场景中进行代码的复用。 - -在本例中,遵循 `FlyBehavior` 和 `QuackBehavior` 类似于定义的算法,然后在 RubberDuck 的 `makeWing` 和 `makeBreaken` 中动态的改变了 `flyBehavior` 和 `quackBehavior` 的行为。 - -上面的例子取自与 《Head First 设计模式》 第一章,主要将的就是策略模式。主要介绍了三大原则: - -+ 封装变化 -+ 多用组合,少用继承 -+ 针对接口编程,不针对实现编程 - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git "a/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\347\256\200\345\215\225\345\267\245\345\216\202\343\200\201\345\267\245\345\216\202\346\226\271\346\263\225\343\200\201\346\212\275\350\261\241\345\267\245\345\216\202.md" "b/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\347\256\200\345\215\225\345\267\245\345\216\202\343\200\201\345\267\245\345\216\202\346\226\271\346\263\225\343\200\201\346\212\275\350\261\241\345\267\245\345\216\202.md" deleted file mode 100644 index bcf8212..0000000 --- "a/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\347\256\200\345\215\225\345\267\245\345\216\202\343\200\201\345\267\245\345\216\202\346\226\271\346\263\225\343\200\201\346\212\275\350\261\241\345\267\245\345\216\202.md" +++ /dev/null @@ -1,363 +0,0 @@ -# 简单工厂、工厂方法、抽象工厂 - -标签(空格分隔): 设计模式 Swift - ---- - -[TOC] - -**简单工厂**不是一个设计模式,而是一直编码习惯 - -**工厂方法**是定义一个创建对象的接口,子类实现该接口,子类决定具体创建哪个对象。 - -**抽象工厂**提供一个接口,用于创建**一系列相关或者相互依赖**的对象,而不需要指定他们的具体类。 - -简单工厂->工厂方法->抽象工厂类似一种层次递进,层层抽象的过程。下面以实际例子来说明; - -> 需求内容依据 《HeadFirst 设计模式》 第三章 - -## 需求一:简单工厂- Pizza 店开张了 - -Pizza 店开张了,店里售卖很多种披萨,披萨的制作流程都一样的:准备(prepare)、烘烤(bake)、切片(cut)、打包(box)。 - -```swift -protocol Pizza { - func prepare() - func bake() - func cut() - func box() -} - -class PizzaStore { - - func orderPizza(type: String) -> Pizza? { - var pizza: Pizza? = nil - - if type == "cheese" { - pizza = CheesePizza() - } else if type == "Greek" { - pizza = GreekPizza() - } - - pizza?.prepare() - pizza?.bake() - pizza?.cut() - pizza?.box() - return pizza - } -} - -class CheesePizza: Pizza { } - -class GreekPizza: Pizza { } - -``` -调用: - -``` -PizzaStore().orderPizza(type: "cheese") - -``` - - -使用协议 Pizza 来定义披萨类型,各种具体的披萨(CheesePizza、GreekPizza)遵循 Pizza 协议,用户通过 PizzaStore 的 orderPizza 预定 Pizza,通过参数 type 来区分不一样的 Pizza 类型。 - -其中通过 type 来判断具体的类型部分: - -``` -if type == "cheese" { - pizza = CheesePizza() -} else if type == "Greek" { - pizza = GreekPizza() -} -``` -当 Pizza 类型添加或者删除的时候,需要增加或者删除这部分代码,这违反了**对修改关闭原则**。 - -使用简单工厂修改: - -``` -protocol PizzaFactory { - func createPizza(type: String) -> Pizza? -} - -class SimplePizzaFactory: PizzaFactory { - func createPizza(type: String) -> Pizza? { - var pizza: Pizza? = nil - - if type == "cheese" { - pizza = CheesePizza() - } else if type == "Greek" { - pizza = GreekPizza() - } - - return pizza - } -} - -class PizzaStore { - - let pizzaFactory: PizzaFactory - init(pizzaFactory: PizzaFactory) { - self.pizzaFactory = pizzaFactory - } - - func orderPizza(type: String) -> Pizza? { - let pizza: Pizza? = self.pizzaFactory.createPizza(type: type) - - pizza?.prepare() - pizza?.bake() - pizza?.cut() - pizza?.box() - return pizza - } -} - -``` -调用 - -``` -PizzaStore(pizzaFactory: SimplePizzaFactory()).orderPizza(type: "cheese") -``` -简单工厂把根据 Type 创建 Pizza 的变化部分放到 PizzaFactory 中。PizzaFactory 专职创建 Pizza。 - -简单工厂看似是将一个内容转移到另一个对象中,但是如果创建代码的复用,优势就明显。(PS: **实际**开发中,一般也不会这么做,因为这可能会引入一个新的类文件) - - -## 需求二: 工厂方法-加盟披萨店 -披萨店扩展很快,现在在不同的地方都有不同的加盟店,但是这也带来一个问题,每个地区的披萨类型口味不一致。 - -最简单的做法是创建不同地区的 Pizza 工厂,创建各自风味 Pizza - -```swift -/// New York 披萨工厂方法,创建 New York 风味的 Pizza -class NYPizzaFactory: PizzaFactory { - func createPizza(type: String) -> Pizza? { - var pizza: Pizza? = nil - - if type == "cheese" { - pizza = NYCheesePizza() - } else if type == "Greek" { - pizza = NYGreekPizza() - } - - return pizza - } -} - -/// Chicago 披萨工厂方法,创建 Chicago 风味的 Pizza -class ChicagoPizzaFactory: PizzaFactory { - func createPizza(type: String) -> Pizza? { - var pizza: Pizza? = nil - - if type == "cheese" { - pizza = ChicagoCheesePizza() - } else if type == "Greek" { - pizza = ChicagoGreekPizza() - } - - return pizza - } -} - -class NYCheesePizza: Pizza { } - -class NYGreekPizza: Pizza { } - -class ChicagoCheesePizza: Pizza { } - -class ChicagoGreekPizza: Pizza { } - -``` -调用 - -``` -PizzaStore(pizzaFactory: NYPizzaFactory()).orderPizza(type: "cheese") -PizzaStore(pizzaFactory: ChicagoPizzaFactory()).orderPizza(type: "cheese") -``` -上面的做法是对每个地区生成各自的工厂,然后由各自的工厂生成各自风味的 Pizza。 - -在这里回顾下工厂方法的定义:**一个创建对象的接口,子类实现该接口,子类决定具体创建哪个对象** - -在这里我们的确定义了工厂的接口(PizzaFactory),然后让子类(NYPizzaFactory、ChicagoPizzaFactory)遵循这个接口,各自工厂决定如何创建 Pizza 对象。 - -### 讨论(如果没有看书可以直接忽略掉这部分内容) -《Head First 设计模式》不是将工厂而是将 PizzaStore 进行了抽象,然后将上面工厂的方法强行塞到各自的 PizzaStore 中;然后又将 OrderPizza 与 CretePizza 强行绑定到 PizzaStore 中,虽然最后 PizzaStore 设计为抽象类型,但是依旧避免不了在**简单工厂**带来的问题。 - -作者在书中提到 - -> 在简单工厂中,工厂是另一个由 PizzaStore 使用的对象。 -- P135 第二个问题 -> -> 子类的确看起来很像简单工厂。简单工厂把全部的事情,在一个地方都处理完了,然而工厂方法却是创建一个框架,让子类决定如何实现。 --- P135 最后一个问题 - -对于上面的解释,个人感觉有点牵强,**因此这里并不采用书上内容重新创建 PizzaStore 模式**。 - -欢迎讨论。 - - -## 需求三 抽象工厂-重新回到 Pizza 本身 - -这里将会讲两个实例:1. Pizza 实例; 2. 家具实例 - -### 3.1 Pizza 实例 - -**这个实例部分参考书本上内容** - -通过上面的工厂,可以创建出不同风味的 Pizza,但是对于同一种 Pizza,每个地区使用的配料有可能就不一样,为了风味统一纯正(NY的Pizza 需要用 NY 的面团以及 NY 的酱汁,而不能使用 NY 的面团和 Chicago 的酱汁),必须加以限制。 - -通过产生一个成分配方工厂,产生对应的面团以及酱汁风味: - -```swift - -class NYDough: Dough { } -class NYSauce: Sauce { } - -class ChicagoDough: Dough { } -class ChicagoSauce: Sauce { } - -/// 原料工厂 -protocol PizzaIngredientFactory { - func makeDough() -> Dough - func makeSauce() -> Sauce -} - -class NYPizzaIngredientFactory: PizzaIngredientFactory { - func makeDough() -> Dough { - return NYDough() - } - - func makeSauce() -> Sauce { - return NYSauce() - } -} - -class ChicagoPizzaIngredientFactory: PizzaIngredientFactory { - func makeDough() -> Dough { - return ChicagoDough() - } - - func makeSauce() -> Sauce { - return ChicagoSauce() - } -} - -``` - -在 PizzaFactory 以及 Pizza实例中 - -``` -/// New York 披萨工厂方法,创建 New York 风味的 Pizza -class NYPizzaFactory: PizzaFactory { - - let pizzaIngredientFactory = NYPizzaIngredientFactory() - - func createPizza(type: String) -> Pizza? { - var pizza: Pizza? = nil - - if type == "cheese" { - pizza = NYCheesePizza(ingredientFactory: self.pizzaIngredientFactory) - } else if type == "Greek" { - pizza = NYGreekPizza(ingredientFactory: self.pizzaIngredientFactory) - } - - return pizza - } -} - -class NYCheesePizza: Pizza { - - let ingredientFactory: PizzaIngredientFactory - init(ingredientFactory: PizzaIngredientFactory) { - self.ingredientFactory = ingredientFactory - } - - func prepare() { - debugPrint(" NYCheese Prepare ") - debugPrint("using douch: \(self.ingredientFactory.makeDough())" ) - debugPrint("using Sauch: \(self.ingredientFactory.makeSauce())" ) - - } - func bake() { debugPrint("NYCheese Bake") } - func cut() { debugPrint("NYCheese Cut") } - func box() { debugPrint("NYCheese Box") } -} - -class NYGreekPizza: Pizza { - let ingredientFactory: PizzaIngredientFactory - init(ingredientFactory: PizzaIngredientFactory) { - self.ingredientFactory = ingredientFactory - } - - func prepare() { - debugPrint(" NYGreek Prepare ") - debugPrint("using douch: \(self.ingredientFactory.makeDough())" ) - debugPrint("using Sauch: \(self.ingredientFactory.makeSauce())" ) - - } - - func bake() { debugPrint("NYGreek Bake") } - func cut() { debugPrint("NYGreek Cut") } - func box() { debugPrint("NYGreek Box") } -} - -``` - -对外界调用来说,并没有什么改变,只不过内部更多的限制而已 - -``` -PizzaStore(pizzaFactory: NYPizzaFactory()).orderPizza(type: "cheese") -PizzaStore(pizzaFactory: ChicagoPizzaFactory()).orderPizza(type: "cheese") -``` - -### 3.2 家具实例 - -r如果你进行装修,你肯定是需要选择合适的风格:中式、欧美等,你肯定不会选择中式的桌子搭配欧式的椅子吧,因此需要对的桌子椅子进行限制。 - -``` - -protocol Table {} -protocol Chair {} - -class ChineseDiningTable: Table {} -class ChineseChair: Chair {} - -class EuropeanDiningTable: Table {} -class EuropeanChair: Chair {} - - -protocol FurnitureFactory { - func makeTable() -> Table - func makeChair() -> Chair -} - -class ChineseFurnitureFactory: FurnitureFactory { - func makeTable() -> Table { - return ChineseDiningTable() - } - - func makeChair() -> Chair { - return ChineseChair() - } -} - - -class EuropeanFurnitureFactory: FurnitureFactory { - func makeTable() -> Table { - return EuropeanDiningTable() - } - - func makeChair() -> Chair { - return EuropeanChair() - } -} - -``` - -抽象工厂主要是提供一个接口,用于创建**一系列相关或者相互依赖**的对象,这下应该理解了吧。 - - - -## 讨论 - 碎碎念 - -书中一直在强调**面向接口编程,而不是面向实现编程**, **依赖抽象,不要依赖具体(依赖倒置原则)**。但这有利也有弊,抽象出去后进行了解耦,但是也无法对具体对象进行额外特殊处理了,这一点跟**装饰器**一节最后提到的半透明装饰器一样。 - - diff --git "a/Design Pattern/\350\243\205\351\245\260\345\231\250\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\347\273\223\346\236\204\345\236\213/\350\243\205\351\245\260\345\231\250\350\256\276\350\256\241\346\250\241\345\274\217.md" similarity index 100% rename from "Design Pattern/\350\243\205\351\245\260\345\231\250\350\256\276\350\256\241\346\250\241\345\274\217.md" rename to "docs/\350\256\276\350\256\241\346\250\241\345\274\217/\347\273\223\346\236\204\345\236\213/\350\243\205\351\245\260\345\231\250\350\256\276\350\256\241\346\250\241\345\274\217.md" diff --git "a/Design Pattern/\351\200\202\351\205\215\345\231\250\346\250\241\345\274\217.md" "b/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\347\273\223\346\236\204\345\236\213/\351\200\202\351\205\215\345\231\250\346\250\241\345\274\217.md" similarity index 100% rename from "Design Pattern/\351\200\202\351\205\215\345\231\250\346\250\241\345\274\217.md" rename to "docs/\350\256\276\350\256\241\346\250\241\345\274\217/\347\273\223\346\236\204\345\236\213/\351\200\202\351\205\215\345\231\250\346\250\241\345\274\217.md" diff --git "a/Design Pattern/\345\221\275\344\273\244\346\250\241\345\274\217.md" "b/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\350\241\214\344\270\272\345\236\213/\345\221\275\344\273\244\346\250\241\345\274\217.md" similarity index 99% rename from "Design Pattern/\345\221\275\344\273\244\346\250\241\345\274\217.md" rename to "docs/\350\256\276\350\256\241\346\250\241\345\274\217/\350\241\214\344\270\272\345\236\213/\345\221\275\344\273\244\346\250\241\345\274\217.md" index e62bdd8..2b405a4 100644 --- "a/Design Pattern/\345\221\275\344\273\244\346\250\241\345\274\217.md" +++ "b/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\350\241\214\344\270\272\345\236\213/\345\221\275\344\273\244\346\250\241\345\274\217.md" @@ -4,8 +4,6 @@ --- -[TOC] - **命令行模式是将请求(或称为方法调用)封装成对象**,让动作的请求者与动作执行者解耦,动作的请求者无需知道具体的动作执行者,动作请求者只要知道如何发起请求即可。 命令行模式涉及如下对象: diff --git "a/Design Pattern/\347\255\226\347\225\245\346\250\241\345\274\217.md" "b/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\350\241\214\344\270\272\345\236\213/\347\255\226\347\225\245\346\250\241\345\274\217.md" similarity index 100% rename from "Design Pattern/\347\255\226\347\225\245\346\250\241\345\274\217.md" rename to "docs/\350\256\276\350\256\241\346\250\241\345\274\217/\350\241\214\344\270\272\345\236\213/\347\255\226\347\225\245\346\250\241\345\274\217.md" diff --git "a/Design Pattern/\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md" "b/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\350\241\214\344\270\272\345\236\213/\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md" similarity index 100% rename from "Design Pattern/\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md" rename to "docs/\350\256\276\350\256\241\346\250\241\345\274\217/\350\241\214\344\270\272\345\236\213/\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md" diff --git "a/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\350\243\205\351\245\260\345\231\250\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\350\243\205\351\245\260\345\231\250\350\256\276\350\256\241\346\250\241\345\274\217.md" deleted file mode 100644 index 19061f7..0000000 --- "a/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\350\243\205\351\245\260\345\231\250\350\256\276\350\256\241\346\250\241\345\274\217.md" +++ /dev/null @@ -1,227 +0,0 @@ -# 装饰器设计模式 - -标签(空格分隔): 设计模式 Swift - ---- - -[TOC] - -> 需求内容依据 《HeadFirst 设计模式》 第三章 - -装饰器是**动态**地往一个类别中添加新的行为的设计模式,是一种比继承**更有弹性**的替代方案。 - -> **维基百科:** -> -> 通过使用修饰模式,可以在运行时扩充一个类别的功能。原理是:增加一个修饰类包裹原来的类别,包裹的方式是在修饰类的构造函数中将原来的类以参数的形式传入。装饰类实现新的功能,但是,在不需要用到新功能的地方,它可以直接调用原来的类别中的方法。修饰类必须和原来的类别有相同的接口。 -> ->修饰模式是类别继承的另外一种选择。类继承在编译时候增加行为,而装饰模式是在运行时增加行为。 - - -## 需求: 给星巴兹设计订单系统 -星巴兹是快速扩张的咖啡连锁店,为了管理目的,他们需要设计自己的订单系统。 - -``` -/// 饮料系统(超类) -class Beverage { - - /// 描述信息 - var description: String { return "" } - - /// 返回每种饮料的价格信息 - func cost() -> Double { return 0.0 } -} - -/// 子类继承重写对应方法 -class HouseBlend: Beverage { - override var description: String { return "House Blend" } - - override func cost() -> Double { return 10.0 } -} - -class DarkRoast: Beverage { - override var description: String { return "Dark Roast" } - - override func cost() -> Double { return 20.0 } -} -``` -上面这种继承的方式对于简单系统来说可行,但是对于复杂的饮料系统来说,每种咖啡必须要考虑到各种调料:例如蒸奶(Steamed Milk)、豆浆(Soy)、摩卡(Mocha)等,因此需要设计不一样的子类,如 `HouseBlendWithSteamedMilk`、`HouseBlendWithSteamedMilkMocha` 等子类,然后子类重写对应的计算方式。这种方式带来两个严重的问题: -1. 类爆炸:每新增一种调料需要增加几十上百种子类; -2. 当某种调料价格上涨,需要需改对应的子类的价格。 - -这严重违反了**针对接口编程**以及**多用组合,少用继承的**的设计原则。当然在实际开发中,我也相信不会有人这样进行设计。 - -那如何来设计对应的系统呢? - -**方案一:**将每一种的调料放到各自的饮料中,例如 `HouseBlend` 有蒸奶(Steamed Milk)和摩卡(Mocha): - -``` -class DarkRoast: Beverage { - /// 是否有调料 - var hasSteamedMilk: Bool = false - var hasMocha: Bool = false - - override var description: String { return "DarkRoast" } - - /// 重写了价格,考虑了调料价格 - override func cost() -> Double { - var cost = 10.0 - if hasSteamedMilk { cost += 5.0 } - if hasMocha { cost += 1.0 } - - return cost - } -} -``` -这样可以减少因为调料而扩展的子类,但这也有个灾难,当某种调料价格变化,这个里面也需要跟着变化,无穷无尽。 - -**方案二:**将各种调料放到基类 `Beverage` 中 - -``` -class Beverage { - /// 调料份数 - var steamedMilk: Double = 0 - var mocha: Double = 0 - var milk: Double = 0 - - func cost() -> Double { - var cost = 0.0 - cost += ( steamedMilk * 5.0 ) - cost += ( mocha * 1.0 ) - cost += ( milk * 2.0 ) - return 0.0 - } -} - -/// 子类继承重写对应方法 -class HouseBlend: Beverage { - override func cost() -> Double { - var cost = super.cost() - cost += 10.0 - return cost - } -} -``` - -通过父类 `Beverage` 将所有的调料份数放入其中,然后计算价格。子类重写 `cost()` 方法。 - -但这也会存在几个问题: -1. 如果某种调料价格改变后,我们需要更改 `Beverage` 的计算逻辑;--- 违反开闭原则 -2. 如果出现新的调料或者删除某种调料,需要更改 `Beverage` 以及对应的计算逻辑。-- 违反开闭原则 -3. 对于新的饮料某些调料并不适合,**继承会让子类拥有一些不适合自己的行为属性。**如茶里面加奶泡。 - -## 装饰器设计模式重构系统 - -![](https://tva1.sinaimg.cn/large/e6c9d24egy1h4mzrh820hj21gl0u0djx.jpg) - -通过上图可以看到装饰器功能 - -1. 以 DarkRoast 为对象开始; -2. 用户先要摩卡(Mocha),然后使用 Mocha 将 DarkRoast 装饰起来 -3. 用户想要奶泡(Whip),使用 Whip 将 Mocha 装饰起来 - -当计算价格的时候,直接计算 Whip 的价格,然后递归计算内部的 Mocha、DarkRoast 的价格。 - -``` -protocol Beverage { - func cost() -> Double -} - -class DarkRoast: Beverage { - func cost() -> Double { return 20.0 } -} - -protocol CondimentDecorator: Beverage {} - -class Mocha: CondimentDecorator { - var beverage: Beverage - - init(beverage: Beverage) { - self.beverage = beverage - } - - func cost() -> Double { - return 1.0 + self.beverage.cost() - } -} - -class Whip: CondimentDecorator { - var beverage: Beverage - - init(beverage: Beverage) { - self.beverage = beverage - } - - func cost() -> Double { - return 2.0 + self.beverage.cost() - } -} - -``` - -调用地方: -``` - -/// 使用超类 Beverage -var beverage: Beverage = DarkRoast() -beverage = Mocha(beverage: beverage) -beverage = Whip(beverage: beverage) - -print(beverage.cost()) -``` -在这里,我们直接将 `Beverage` 和 `CondimentDecorator` 设计为接口(面向接口编程)。对用户来说,使用装饰者(Mocha 和 Whip)替代被装饰者(DarkRoast)是无感的,**因为装饰者和被装饰对象有相同的类型**。这样达到了**在不修改代码的情况下,对原有对象(Beverage)进行功能的扩展。** - -## 装饰器类图 - -注意:装饰者与被装饰者有相同的类型即可 - -![](https://tva1.sinaimg.cn/large/e6c9d24egy1h4n19wjhitj20hg0abaal.jpg) - -图来自网络 - - -## 半透明装饰器 -标准装饰器是在不改变原有接口情况下对其进行功能扩展,这样可以随时使用装饰者替换被装饰者。但在实际的使用中,也可以通过装饰器进行扩展新的功能。这样就称为半透明模式。例如:对购买摩卡咖啡的用户可以随机选择一个礼物功能: - -``` -class Mocha: CondimentDecorator { - var beverage: Beverage - - init(beverage: Beverage) { - self.beverage = beverage - } - - func cost() -> Double { - return 1.0 + self.beverage.cost() - } - - func gift() -> String { - return "杯具" - } -} -``` - -## 优缺点: -**优点:**可以动态的给对象增加功能 -**缺点:**装饰器过容易产生更多的对象 - - - - - - - - - - - - - - -## 参考链接: -1. java设计模式-装饰器模式(Decorator): https://www.jianshu.com/p/d80b6b4b76fc -2. 维基百科-修饰模式: https://zh.wikipedia.org/zh-cn/%E4%BF%AE%E9%A5%B0%E6%A8%A1%E5%BC%8F -3. 重学 Java 设计模式:实战装饰器模式 https://www.cnblogs.com/xiaofuge/p/13082966.html - - - - diff --git "a/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md" "b/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md" deleted file mode 100644 index df53907..0000000 --- "a/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md" +++ /dev/null @@ -1,129 +0,0 @@ -# 观察者模式 - -标签(空格分隔): 设计模式 Swift - ---- - -> 本文实例取自于 《Head First 设计模式》 第二章 - -观察者模式是**一对多**的设计模式。观察者(Observer)监听主题(Subject)变化。当主题(Subject)状态发生变化后,会通知所有的观察者。**主题与观察者之间松耦合** - - -**需求:**显示布告栏需要箭头气象变化,实时显示气压、温度和湿度变化。 - -![](https://tva1.sinaimg.cn/large/e6c9d24egy1h4fg8jnitaj20hw0am74t.jpg) - -我们可以从 WeatherData 结构中获取对应的数据 - -```swift -protocol Observer: AnyObject { - func update() -} - -protocol Subject { - func registerObserver(o: Observer) - func removeObserver(o: Observer) - func notifyObserver() -} - -class WeatherData: Subject { - - private var observers: [Observer] = [] - - func registerObserver(o: Observer) { - self.observers.append(o) - } - - func removeObserver(o: Observer) { - if let index = self.observers.firstIndex(where: { $0 === o }) { - self.observers.remove(at: index) - } - } - - func notifyObserver() { - self.observers.forEach { $0.update() } - } - - public private(set) var temperature: Double = 0.0 - public private(set) var humidity: Double = 0.0 - public private(set) var pressure: Double = 0.0 - - func measureMentsChanged() { - notifyObserver() - } - - func setMeasureChanged(temperature: Double, humidity: Double, pressure: Double) { - self.temperature = temperature - self.humidity = humidity - self.pressure = pressure - - measureMentsChanged() - } -} - -class ConcreteObserverA: Observer { - weak var weatherData: WeatherData? - func update() { - print(self.weatherData?.humidity ?? 0, self.weatherData?.temperature ?? 0) - } -} - -``` - -测试代码: -``` -let weather = WeatherData() -weather.registerObserver(o: ConcreteObserverA()) - -weather.setMeasureChanged(temperature: 10.0, humidity: 20.0, pressure: 30.0) -``` - -为了避免类继承,在这里将观察者(Observer)与主题(Subject)都设计为协议,但这也给 `update()` 更新方法带来困难。 - -观察者收到更新消息后,大多情况会拉去最新的数据,这里就有两种方式: - -1. 观察者自己去**拉**去最新数据; -2. 主题主动**推**最新的数据。 - -针对**拉**,因为涉及为协议内容,那么 `ConcreteObserverA` 需要知道具体主题对象内容,上面代码中就是这样处理的。 - -针对**推**, 那么需要在 `update()` 方法中把状态或者主题发送出来,然后进行处理 - -拉或者推,这两种方式各有优劣,需要根据实际情况来决定。 - -``` -// 主题当做参数传出来 -func update(s: Subject) { - if let weather = s as? WeatherData { - print(weather.humidity, weather.temperature) - } -} - -// 状态传递出来 -func update(temperature: Double, humidity: Double, pressure: Double) { -} - -``` - -### 延伸内容:设计主题更新策略 - -实际情况中,主题更新需要一些策略,例如多少时间内有更新才发送或者变化超过一定范围才会发送,这就涉及到对应的策略问题。 - -书中提到在主题方法中,配置一个 `var changed: BOOL` 可以让订阅者控制让主题更新。 - -```swift -public var changed: Bool = true - -func notifyObserver() { - self.observers.forEach { $0.update(s: self) } -} - -func measureMentsChanged() { - if changed { - notifyObserver() - changed = false - } -} -``` -**个人认为这并不是一个好的策略**,观察者模式是一对多的,每个观察者的策略可能不相同,这种策略适宜放在观察者中。 - diff --git "a/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\351\200\202\351\205\215\345\231\250\346\250\241\345\274\217.md" "b/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\351\200\202\351\205\215\345\231\250\346\250\241\345\274\217.md" deleted file mode 100644 index bd52b20..0000000 --- "a/docs/\350\256\276\350\256\241\346\250\241\345\274\217/\351\200\202\351\205\215\345\231\250\346\250\241\345\274\217.md" +++ /dev/null @@ -1,101 +0,0 @@ -# 适配器模式 - -标签(空格分隔): 设计模式 Swift C++ - ---- - -[TOC] - -**适配器模式**是将一个接口转换为另外一个接口。从而让不兼容的接口变得兼容。 - -![](https://tva1.sinaimg.cn/large/e6c9d24egy1h4y7ciunfpj20kz08i0t0.jpg) - -在上图中,现有系统无法与厂商类相结合,因此需要使用适配器将现有系统转换为厂商类。从上图可以看出,适配器应该遵循最终的厂商类接口。 - -一般而言,适配器有两种类型: - -+ 对象适配器 -+ 类适配器 - -## 对象适配器-鸭子模型 - -在 Python 或者 Go 语言中,鸭子类型使用极其广泛,[鸭子测试](https://zh.m.wikipedia.org/zh-hans/%E9%B8%AD%E5%AD%90%E7%B1%BB%E5%9E%8B)如下表述: - -> “当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。” - -在 Swift 等静态语言中,我们可以使用适配器来将**鸟**适配为**鸭子**。 - -``` -protocol Duck { - func swim() - func quack() - func walk() -} - -class Bird { - func walk() { debugPrint( "bird walk" ) } - func tweet() { debugPrint( "bird tweet" ) } - func bathe() { debugPrint( "bird bathe" ) } -} - -class BirdAdapter: Duck { - let bird: Bird - - init(bird: Bird) { - self.bird = bird - } - - func swim() { - self.bird.bathe() - } - - func quack() { - self.bird.tweet() - } - - func walk() { - self.bird.walk() - } -} -``` - -程序代码比较简单,我们定义了一个鸭子的协议类型,其中包含了 `swim`、`quack` 以及 `walk` 方法。直接让 Bird 遵守 Duck 是不太合适的。因此使用了中间适配器 `BirdAdapter`。 - -调用地方 -``` - -let bird = Bird() -let duck = BirdAdapter(bird: bird) - -duck.quack() -duck.walk() -duck.swim() -``` - -适配器比较简单,客户使用适配器的过程如下: - -1. 客户通过目标接口(Duck)调用适配器(BirdAdapter)方法对适配器发出请求; -2. 适配器使用被适配者(Bird)接口把请求转换为被适配着的调用接口;`self.bird.bathe()`、`self.bird.tweet()`、`self.bird.walk()` -3. 客户收到调用的结果,但并未察觉这一切是适配器在起作用; - - -## 类适配器 - -类适配器需要使用多继承来完成。类适配器需要让 Adapter 直接继承于两个类型,当请求 request 的时候,Adapter 重写 Target 的 request 方法,然后内部转化为 Adaptee 的 specificRequest 方法。 - -![](https://tva1.sinaimg.cn/large/e6c9d24egy1h521j0oljpj21710u0ab7.jpg) - -像 Java、Swift、OC等语言没有多继承的概念,因此很少用到,C++ 存在多继承,但是多继承容易带来菱形继承等复杂问题。不建议使用。 - -在另一方面,对象适配器使用的是组合的方式,而类适配器使用的是继承。 -如果你熟练于针对接口编程,那么你就可以轻易的将类适配器转换为对象适配器。只需要将其中的一个类型转换为接口即可。在一些博客中,示例程序也是使用该方法实现。 - -## 讨论:装饰器与适配器 - -装饰器和适配器很像,都是对现有的一个对象进行响应的操作。但这两者还是有本质区别。 - -适配器的本质是让两个**不兼容**的接口进行转换,从而让两者兼容。 - -装饰器是对现有对象行为进行的扩展,半透明装饰器会对现有对象进行行为的增加,但是装饰器与现有对象应该是遵循相同的接口。 - - diff --git a/mkdocs.yml b/mkdocs.yml index 8b7acee..1696eee 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -3,10 +3,10 @@ nav: - Home: index.md - About: about.md - 设计模式: - - 创建型-简单工厂、工厂方法、抽象工厂: 设计模式/简单工厂、工厂方法、抽象工厂.md - - 结构型-装饰器设计模式: 设计模式/装饰器设计模式.md - - 结构型-适配器模式: 设计模式/适配器模式.md - - 行为型-命令模式: 设计模式/命令模式.md - - 行为型-策略模式: 设计模式/策略模式.md - - 行为型-观察者模式: 设计模式/观察者模式.md + - 创建型-简单工厂、工厂方法、抽象工厂: 设计模式/创建型/简单工厂、工厂方法、抽象工厂.md + - 结构型-装饰器设计模式: 设计模式/结构型/装饰器设计模式.md + - 结构型-适配器模式: 设计模式/结构型/适配器模式.md + - 行为型-命令模式: 设计模式/行为型/命令模式.md + - 行为型-策略模式: 设计模式/行为型/策略模式.md + - 行为型-观察者模式: 设计模式/行为型/观察者模式.md diff --git "a/Data Structure/\346\225\260/delete.cpp" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260/delete.cpp" similarity index 100% rename from "Data Structure/\346\225\260/delete.cpp" rename to "\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260/delete.cpp" diff --git "a/Data Structure/\346\225\260/enumerate.cpp" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260/enumerate.cpp" similarity index 100% rename from "Data Structure/\346\225\260/enumerate.cpp" rename to "\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260/enumerate.cpp" diff --git "a/Data Structure/\346\225\260/insert.cpp" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260/insert.cpp" similarity index 100% rename from "Data Structure/\346\225\260/insert.cpp" rename to "\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260/insert.cpp" diff --git "a/Data Structure/\346\225\260/search.cpp" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260/search.cpp" similarity index 100% rename from "Data Structure/\346\225\260/search.cpp" rename to "\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260/search.cpp" diff --git "a/Data Structure/\346\225\260/\346\225\260-\345\237\272\346\234\254\345\206\205\345\256\271.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260/\346\225\260-\345\237\272\346\234\254\345\206\205\345\256\271.md" similarity index 100% rename from "Data Structure/\346\225\260/\346\225\260-\345\237\272\346\234\254\345\206\205\345\256\271.md" rename to "\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260/\346\225\260-\345\237\272\346\234\254\345\206\205\345\256\271.md" diff --git "a/Data Structure/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225\357\274\2101\357\274\211-- \346\227\266\351\227\264\345\244\215\346\235\202\345\272\246\344\270\216\347\251\272\351\227\264\345\244\215\346\235\202\345\272\246.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225\357\274\2101\357\274\211-- \346\227\266\351\227\264\345\244\215\346\235\202\345\272\246\344\270\216\347\251\272\351\227\264\345\244\215\346\235\202\345\272\246.md" similarity index 100% rename from "Data Structure/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225\357\274\2101\357\274\211-- \346\227\266\351\227\264\345\244\215\346\235\202\345\272\246\344\270\216\347\251\272\351\227\264\345\244\215\346\235\202\345\272\246.md" rename to "\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225\357\274\2101\357\274\211-- \346\227\266\351\227\264\345\244\215\346\235\202\345\272\246\344\270\216\347\251\272\351\227\264\345\244\215\346\235\202\345\272\246.md"