diff --git a/.DS_Store b/.DS_Store index fc40437..c0d1ca4 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..8d46ef0 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,38 @@ +name: Build and Deploy docs + +on: + push: + branches: + - main # 监听 main 分支的提交,或根据需要更改为其他分支 + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + + steps: + # 检出源代码 + - name: Checkout code + uses: actions/checkout@v3 + + # 安装 Python 和 MkDocs + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.13.0' # 可以选择其他 Python 版本 + + - name: Install MkDocs and dependencies + run: | + python3 -m pip install --upgrade pip + pip install mkdocs + + # 构建静态网站 + - name: Build site with MkDocs + run: mkdocs build --site-dir site + + - name: Deploy + uses: s0/git-publish-subdir-action@develop + env: + REPO: git@github.com:Xiangao2018/xiangao2018.github.io.git + BRANCH: gh-pages + FOLDER: site + SSH_PRIVATE_KEY: ${{ secrets.PAGS_TOKEN }} diff --git a/docs/about.md b/docs/about.md new file mode 100644 index 0000000..ce57875 --- /dev/null +++ b/docs/about.md @@ -0,0 +1,3 @@ +# About + +Hi, My name is Xiangao2018, Welcome to my webSite \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..e357618 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,17 @@ +# Home + +For full documentation visit [mkdocs.org](https://www.mkdocs.org). + +## Commands + +* `mkdocs new [dir-name]` - Create a new project. +* `mkdocs serve` - Start the live-reloading docs server. +* `mkdocs build` - Build the documentation site. +* `mkdocs -h` - Print help message and exit. + +## Project layout + + mkdocs.yml # The configuration file. + docs/ + index.md # The documentation homepage. + ... # Other markdown pages, images and other files. 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" new file mode 100644 index 0000000..e62bdd8 --- /dev/null +++ "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" @@ -0,0 +1,565 @@ +# 命令模式 + +标签(空格分隔): 设计模式 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" new file mode 100644 index 0000000..6ef1cd3 --- /dev/null +++ "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" @@ -0,0 +1,285 @@ +# 策略模式 + +标签(空格分隔): 设计模式 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" new file mode 100644 index 0000000..bcf8212 --- /dev/null +++ "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" @@ -0,0 +1,363 @@ +# 简单工厂、工厂方法、抽象工厂 + +标签(空格分隔): 设计模式 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/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" new file mode 100644 index 0000000..19061f7 --- /dev/null +++ "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" @@ -0,0 +1,227 @@ +# 装饰器设计模式 + +标签(空格分隔): 设计模式 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" new file mode 100644 index 0000000..df53907 --- /dev/null +++ "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" @@ -0,0 +1,129 @@ +# 观察者模式 + +标签(空格分隔): 设计模式 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" new file mode 100644 index 0000000..bd52b20 --- /dev/null +++ "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" @@ -0,0 +1,101 @@ +# 适配器模式 + +标签(空格分隔): 设计模式 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 new file mode 100644 index 0000000..6e68235 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,14 @@ +site_name: Xiangao2018 的个人博客 +nav: + - Home: index.md + - About: about.md + - 设计模式: + - 创建型-简单工厂、工厂方法、抽象工厂: 设计模式/简单工厂、工厂方法、抽象工厂.md + - 结构型-装饰器设计模式: 设计模式/装饰器设计模式.md + - 结构型-适配器模式: 设计模式/适配器模式.md + - 行为型-命令模式: 设计模式/命令模式.md + - 行为型-策略模式: 设计模式/策略模式.md + - 行为型-观察者模式: 设计模式/观察者模式.md + + - C++: +