前言
设计模式是有用的抽象化工具, 用于解决工程和建筑等领域的设计问题. 出于同样的目的, 软件开发领域借用了这一概念, 设计模式是一个对象或类的设计模板, 用于解决特定领域经常发生的问题. 本篇共分 8 部分涉及 22 种设计模式:
对象创建(1~6)
原型模式
简单工厂模式
工厂模式
抽象工厂模式
建造者 (生成器) 模式
单例模式
接口适配 (7~9)
适配器模式
桥接模式
外观模式
对象去耦 (10~11)
中介者模式
观察者模式
抽象集合 (12~13)
组合模式
迭代器模式
行为扩展 (14~16)
访问者模式
装饰者模式
责任链模式
算法封装 (17~19)
模板方法模式
策略模式
命令模式
性能与对象访问 (20~21)
享元模式
代理模式
对象状态 (22)
备忘录模式
对象创建
一, 原型模式
什么是原型模式?
使用原型实例指定创建对象的种类, 并通过复制这个对象创建新的对象.
原型模式其实是通过一个对象为模板创建另外一个可定制的对象, 而且不需要知道任何的创建细节.
什么时候使用原型模式?
需要创建的对象应该独立于其类型和创建方式.
需要实例化的类是在运行时决定的.
不想要与产品层次相对应的工厂层次.
不同类的实例间的差异仅是状态的若干组合. 因此复制相应数量的原型比手工实例化更加方便.
类不容易创建, 比如每个组件可把其他组件作为子节点的组合对象. 复制已有的组合对象并对副本进行修改会更加容易.
深拷贝与浅拷贝
浅拷贝, 指针级拷贝, 拷贝出的新实例依旧指向源内存空间, 不论修改原来的实例或者拷贝出的实例, 都会影响到另一个(因为指针指向同一块内存).
深拷贝, 内存级拷贝, 会开辟新的内存空间, 修改原来的实例或者拷贝的实例, 另一个不受到影响.
iOS 中默认的 - copy 是浅拷贝, 若要深拷贝, 需要遵守 < NSCopying > 协议, 重写 - copyWithZone 方法.
二, 简单工厂模式
什么是简单工厂模式?
简单工厂模式 (Simple Factory Pattern), 又称为静态工厂方法(Static Factory Method) 模式, 在简单工厂模式中, 可以根据参数的不同返回不同类的实例. 简单工厂模式专门定义一个类来负责创建其他类的实例, 被创建的实例通常都具有共同的父类.
简单工厂模式的优点
将对象的创建和对象本身业务处理分离可以降低系统的耦合度, 使得两者修改起来都相对容易.
当你需要什么, 只需要传入一个正确的参数, 就可以获取你所需要的对象, 而无须知道其创建细节.
在调用工厂类的工厂方法时, 由于工厂方法是静态方法, 使用起来很方便, 可通过类名直接调用, 而且只需要传入一个简单的参数即可, 修改参数时无须修改任何源代码.
简单工厂模式的缺点
简单工厂模式最大的问题在于工厂类的职责相对过重, 增加新的产品需要修改工厂类的判断逻辑, 这一点与开闭原则是相违背的.
三, 工厂模式
什么是工厂模式?
定义创建对象的接口, 让子类决定实例化哪一个类. 工厂方法使得一个类的实例化延迟到了子类.
什么时候使用工厂模式?
编译时无法准确预期需要创建对象的类.
类想要其子类决定在运行时创建什么类型的实例.
类有若干辅助类为其子类, 而你想将返回哪个子类这种信息局部化.
工厂模式的优势
和直接创建具体对象相比, 使用工厂方法创建对象算是最佳的做法.
工厂方法让客户端可以要求由工厂方法创建的对象拥有一组共同的行为.
因此, 向类层次结构中引入新的具体产品并不需要修改客户端代码, 因为返回的任何具体对象的接口跟客户端一直使用的接口相同.
Cocoa Touch 中的工厂方法
工厂方法在 Cocoa Touch 中使用极为广泛, 以 NSNumber 为例, 它提供了很多的创建方法, 比如 - numberWithBool 和 - numberWithInt 他们传入不同类型的参数, 获得 NSNumber 实例.
四, 抽象工厂
什么是抽象工厂?
提供一个创建一系列相关或者相互依赖对象的接口, 而无需指定他们的具体类.
如果有多个类共有相同的行为, 但实际实现不同, 则可能需要某种抽象类型做为他们的父类, 抽取其他们共同的行为到父类中.
例如, 我们知道普通的披萨饼是什么样子, 在点餐的时候能预计到端上来的是什么. 当我们说 "出去吃披萨" 时, 这里的 "披萨" 其实就是一个抽象类型, 定义了披萨饼应该共同具有的特征. 但是, 从不同的店我们得到同一披萨饼 (比如意大利披萨饼, 腊肠披萨饼) 的味道大不相同. 因为有太多类型的披萨饼, 我们简单地将其叫做 "披萨饼" 以称呼这种特定类型的食品.
父类的类方法 - getFactory 仅仅只是返回具体的 (合适需求的) 工厂类. 其子类不应该重载这个方法.-getFactory 方法根据配置返回一个具体的工厂实例.
抽象工厂和工厂模式的比较
这两者在很多方面都很相似, 他们都用于相同的目的: 创建对象而不让客户端知晓创建细节, 他们的对比如下:
抽象工厂 | 工厂模式 | |
---|---|---|
创建方式 | 对象组合创建抽象产品 | 类继承创建抽象产品 |
创建种类 | 创建多系列产品 | 创建一种产品 |
如何扩展 | 修改父类的接口才支持新产品 | 子类创建者重载工厂方法以创建新的产品 |
抽象工厂 Demo(地图引擎) https://github.com/qingfengiOS/FactoryMapView.git
五, 建造者 (生成器) 模式
什么是建造者模式 将一个复杂对象的构建与他的表现分离, 使得同样的构建过程可以创建不同的表现.
它可以将一个产品的内部表象与产品的生成过程分割开来, 从而可以使一个建造过程生成具有不同内部表象的产品对象.
如果我们用了建造者模式, 那么用户只需要指定需要建造的类型就可以得到他们的对象实例, 而无需关心建造过程和细节.
在此模式中, 除了用户和其所需的产品, 还有两个重要的角色 Director(指导者)和 Builder(生成器).
Director 知道 Builder 应该建造什么, 以参数向其提供缺少的信息来创建特定产品.
Builder 是一个抽象接口, 声明了一个 - build 方法, 该方法由 ConcreBuilder 实现, 以构造实际的产品, ConcreBuilder 有一个 - getResult 方法, 向客户端返回建造完毕的结果.
何时使用建造者模式
需要创建涉及各种部件的复杂对象. 创建对象的算法应该独立于部件的装配方式. 常见例子是构建组合对象.
构建过程需要以不同的方式 (比如, 部件或者表现不同的组合) 构建对象.
建造者模式和抽象工厂的对比
抽象工厂和建造者模式在 抽象对象创建方有许多相似之处, 但是, 二者却大不相同.
建造者模式关注的是分步骤创建复杂对象, 很多时候, 同一类型的对象可以以不同的方式创建. 建造者模式在多步创建过程的最后一步返回产品.
抽象工厂的重点在于创建简单或者复杂产品的套件. 抽象工厂立即返回产品.
建造者模式 | 抽象工厂 |
---|---|
象的复杂程度 | 构建复杂对象 |
需要的步骤 | 多步创建 |
创建方式种类 | 多种方式创建 |
返回对象的时机 | 创建过程的最后一步 |
创建结果 | 专注一个特定的产品 |
总结
生成器模式能帮助构建涉及部件与表现的各种组合的对象. 没有这一模式, 知道构建对象所需细节的 Director 可能会最终变成一个极其复杂的类. 带有无数用于构建同一类的各种表现的内嵌算法. 而这些算法本应该独立于该对象的组成部分以及他们的装配过程.
建造者模式 Demo(画卡通人) https://github.com/qingfengiOS/QFBuilder
六, 单例模式
何为单例模式
单例模式: 保证一个类仅有一个实例, 并且提供一个访问它的全局访问点.
单例模式几乎是设计模式中最简单的了. 它的意图是使得类的一个对象成为系统中的唯一实例. 要实现这一点, 可以从客户端对其进行实例化开始. 因此, 需要用一种只允许生成对象类唯一实例的机制来 "阻止" 所有想要生成对象的访问.
何时使用单例模式
类只能有一个实例, 而且必须从一个为人熟知的访问点对其进行访问,(比如工厂方法).
这个唯一的实例只能通过子类化进行扩展, 而且扩展的对象不会破坏客户端代码.
Objective-C 实现单例模式
在 OC 中, 单例模式的实现目前分为两种:
原始实现
遵守 < NSCopying > 协议重写 - alloc 方法和 - copy 方法, 考虑线程安全.
GCD 实现
借助 GCD 的 dispatch_once 实现
单例模式 Demo(一个严谨的单例) https://github.com/qingfengiOS/Singleton.git
接口适配
七, 适配器
何为适配器模式
适配器模式, 用于连接两种不同种类的对象, 使其毫无问题地协同工作. 其思想比较简单: 适配器实现客户端所需要的某种接口的行为, 同时, 它又连接到一个具有 (完全) 不同接口行为的对象. 一边是客户端懂得如何使用的目标接口, 另一边是客户端一无所知的被适配者, 适配器在二者之间. 它的主要作用是把被适配者的行为传递给管道另一端的客户端.
定义: 将一个类的接口转换成客户希望的另外一个接口. Adapter 模式使得原本接口不兼容而不能在一起工作的类可以一起工作了.
适配器分类
类适配器
它是通过类继承实现的, 而 Objective-C 有着协议 (Protocol) 这一语言特性, 所以在 Objective-C 中, 类可以实现协议, 同时又继承自父类, 从而达到 C++ 中多继承的效果.
要在 OC 中实现类适配器, 首先需要有定义了客户端要使用的一套行为的协议, 然后用具体的适配器类来实现这个协议, 适配器类同时也继承被适配者.
对象适配器
与上面的类适配器不同, 对象适配器不继承被适配者, 而是组合了一个对他的引用.
类适配器与对象适配器的比较
类适配器 | 对象适配器 |
---|---|
只针对单一具体的 Adaptee 类,把 Adaptee 适配到 target | 可以适配多个 Adaptee 及子类 |
易于重载 Adaptee 的行为,以为是通过直接的子类化进行的适配 | 难以重载,需要借助子类的对象而非 Adaptee 本身 |
只有一个 Adaptee 对象,无需额外的这镇间接访问 Adaptee | 需要额外的指针间接访问 Adaptee 并适配其行为 |
Adaptee: 被适配者
Target: 目标接口
何时使用适配器模式
已有类的接口与需求不匹配
想要一个可复用的类, 该类能够同可能带有不兼容接口的其他类协作.
需要适配一个类的几个不同子类, 可是让每一个子类去子类化一个类适配又不现实, 那么可以使用对象适配器 (委托) 来适配其父类的接口.
八, 桥接模式
何为桥接模式?
将抽象部分与它的实现部分分离, 使它们都可以独立地变化.
所谓抽象与它的实现分离, 这并不是说, 让抽象类与其派生类分离, 因为这没有任何意义. 实现指的是抽象类和它的派生类用来实现自己的对象.
实现系统时可能有多角度分类, 每一种分类都有可能变化, 那么就把这种多角度分离出来, 让他们独立变化, 减少他们之间的耦合.
合成 / 聚合复用原则
继承的弊端
对象的继承关系在编译时就已经定好了, 所以无法在运行时改变从父类继承的实现. 子类的实现与其父类有着非常紧密的依赖关系, 以至于父类实现中任何变化必然会导致子类发生变化. 当需要复用子类时, 如果继承下来的实现不适合新的问题, 则父类必须重写或者被其他更适合的类替换. 这种依赖关系限制了灵活性并最终限制了复用性.
合成 / 聚合复用原则
合成 (也叫做组合, Composition) 和聚合 (Aggregation) 都是关联的特殊种类. 聚合表示一种弱的 "拥有" 关系, 体现的是 A 对象可以包含 B 对象, 但 B 对象不是 A 对象的一部分; 合成则是一种强的 "拥有" 关系, 体现了严格的部分和整体的关系, 部分和整体的生命周期一样.
比如: 大雁有两个翅膀, 翅膀与大雁是部分和整体的关系, 并且他们的生命周期相同, 于是大雁和翅膀就是合成关系. 大雁是群居动物, 每只大雁都属于一个雁群, 一个雁群可以有多只大雁, 所以, 大雁和雁群是聚合关系.
优先使用对象的组合 / 聚合将有助于保持每个类被封装, 并集中在单个任务上. 这样类和类继承层次会保持较小规模, 并且不太可能增长为不可控制的庞然大物.
何时使用桥接模式
不想在抽象与实现之间形成固定的绑定关系(这样可以在运行时按需切换实现)
抽象及其实现都应可以通过子类化独立扩展.
对抽象的实现进行修改不应该影响客户端代码.
如果每个实现需要额外的子类以细化抽象, 则说明有必要把他们分成两个部分.
想在带有不同抽象接口的多个对象之间共享一个实现.
桥接模式 Demo(手机软件系统) https://github.com/qingfengiOS/QFBridge
九, 外观模式
何为外观模式
定义: 为系统中的一组接口提供一个统一的接口, 外观定义一个高层接口, 让子系统更易于使用.
外观模式为子系统中一组不同的接口提供一个统一的接口. 外观定义了上层接口, 通过降低复杂度和隐藏子系统之间的通信及依存关系, 让子系统更易于使用.
何时使用外观模式
子系统正在逐渐变得复杂. 应用模式的过程中演化出许多类. 可以使用外观模式为这些子系统类提供一个更简单的接口.
可以使用外观对子系统进行分层. 每个子系统级别有一个外观作为入口点. 让他们通过其外观进行通信, 可以简化他们的依赖关系.
总结
当程序逐渐变大变复杂的时候, 会有越来越多小型的类从设计和应用模式中演化出来. 如果没有一种简化的方式来使用这些类, 客户端代码会变得越来越复杂和难以理解, 而且难以维护, 外观模式有助于提供一中更为简单的方法来使用子系统中的这些类. 处理这些子系统的默认行为, 可能只是定义在外观中的简单方法, 而不必直接使用这些类.
外观模式 Demo(乘客乘车案例) https://github.com/qingfengiOS/QFFacade
对象去耦
十, 中介者模式
何为中介者模式
定义: 用一个对象来封装一系列对象的交互方式. 中介者使各个对象不需要显示地相互引用, 从而使其耦合松散, 而且可以独立地改变它们之间的交互
面向对象的设计鼓励把行为分散到不同的对象中, 这种分散可能导致对象之间的相互关联. 在最糟糕的情况下, 所有对象都彼此之间了解和相互操作.
虽然把行为分散到不同对象增强了可复用性, 但还是增加的相互关联有减少了获得的益处. 增加的关联使得独享很难或者不能在不依赖其他对象的情况下工作. 应用程序的整体行为可能难以改动, 因为他分布于许多对象.
中介者模式用于解决此类问题, 它定义了一个集中的场所, 对象之间的交互可以在一个中介者对象中处理, 其他对象不必彼此交互, 因此减少了他们之间的依存关系.
何时使用中介者模式
对象之间的交互虽然定义明确但是非常复杂, 导致一组对象彼此相互依赖而且难以理解.
因为对象引用了许多其他对象并且与其通讯, 导致对象难以复用.
想要定制一个分布在多个类中的逻辑或行为, 又不想生成太多子类.
中介者模式的优缺点
中介者模式很容易在系统中使用, 也很容易误用. 当系统出现了 "多对多" 交互复杂的对象群时, 不要急于使用中介者模式, 而要先反思系统的设计是否合理.
优点:
1, 中介者的存在, 减少了各个具体类的耦合度, 使各个具体类和中介者可以独立地改变和复用;
2, 由于把对象如何协作进行了抽象, 将中介者作为一个独立的概念并将其封装在一个对象中, 这样关注的对象就从对象各个本身的行为转移到了他们之间的交互上来, 也就是站在一个更宏观的角度去看待系统.
缺点:
由于中介者控制了集中化, 于是把交互复杂性变为了中介者的复杂性, 这就使得中介者会变得比任何一个具体类都复杂.
总结
中介者模式的应用十分广泛, 组件化应该算是最贴切的中介者模式的应用场景了, 各个组件, 独立开发, 维护, 组件之间使用中间件进行通讯. 详见: iOS 一个轻量级的组件化思路
虽然对于处理系统的行为分散于不同对象并且对象相互依存的情况, 中介者模式非常有用, 但是应该注意避免让中介者类过于庞大而难以维护. 如果已经如此了, 可以考虑使用另外的设计模式把它分解.
中介者模式 Demo(同事对话) https://github.com/qingfengiOS/QFMediator
十一, 观察者模式
何为观察者模式
定义: 定义对象间的一种一对多的依赖关系, 当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并且被自动更新
观察者模式也叫做发布 -- 订阅模式. Observer 从 Subject 订阅通知. ConcreteObserver 实现抽象 Observer 并重载其 update 方法. 一旦 Subject 的实例需要通知 Observer 任何的变更, Subject 会发送 update 消息来通知存储在内部列表中所有注册的 Observer. 在 ConcreteObserver 的 update 方法的实际实现中, Subject 的内部状态可被取得并在以后进行处理.
何时使用观察者模式
有两种抽象类型相互依赖. 将它们封装在各自的对象中, 就可以对它们单独进行改变和复用.
对一个对象的改变需要同时改变其他对象, 而不知道具体有多少对象有待改变.
一个对象必须通知其他对象, 而它又不需要知道其他对象是什么.
Cocoa Touch 框架中的观察者模式
通知
Cocoa Touch 框架中使用 NSNotificationCenter 和 NSNotification 对象实现了一对多的发布 -- 订阅模式, 他们允许主题与观察者以一种松耦合的方式通信. 两者在通信时对另外一方无需多少了解.
键 - 值观察
Cocoa 提供了另外一种称为键 - 值观察的机制, 对象之间可以通过它得到其他对象特定的变更通知. 这种机制在 MVC(Model-View-Controller)模式的场景中尤其重要, 它让视图对象可以经由控制器层观察模型对象的变更.
知 | 键 - 值观察 |
---|---|
个中心对象为所有观察者提供变更通知 | 被观察的对象直接向观 察者发送通知 |
要从广义上关注程序事件 | 绑定于特定对象属性的值 |
抽象集合
十二, 组合模式
什么是组合模式
定义: 将对象组合成树形结构以表示 "部分 - 整体" 的层次结构. 组合模式使得用户对单个对象和组合对象的使用具有一致性.
组合模式让我们可以把相同基类型 (base type) 的对象组合到树状结构中, 其中父节点包含同类型的子节点. 换句话说, 这种树状结构形成 "部分 - 整体" 的层次结构.
树形结构是既包含对象的组合 (容器) 又包含作为叶节点 (基元) 的单个对象对的一种层次结构. 每个组合体包含的节点可以是叶节点也可以是其他组合体. 这种关系在这个层次结构中递归重复. 客户端对组合结点和叶节点拥有相同的操作, 客户端在使用时可以忽略他们之间的差别.
何时使用组合模式
想获得对象抽象的树形结构(部分 - 整体层次结构).
想让客户端统一处理组合结构中的所有对象.
透明方式与安全方式
透明方式
在叶节点中也有 - add 和 - remove 方法, 然而叶节点上不需要这些行为; 这样做的目的是为了让他们完全相同的接口, 调用方完全用处理这种逻辑, 这就是透明方式.
安全方式
如果不希望叶节点上存在上面的方法, 那么在最基本结构 (Component) 中就不声明 - add 和 - remove 方法, 而是另外在声明一个结构 (Composite) 用来管理子类对象的方法, 这样就能避免透明代理出现的问题, 但同时, 由于不够透明, 所以树叶和树枝拥有不同的接口, 客户端的调用需要做相应的判断, 给调用方带来了不便.
两种方式没有绝对的优劣, 看个人的理解和取舍(类比简单工厂和工厂模式).
组合模式 Demo(公司组织架构案例) https://github.com/qingfengiOS/QFComposite.git
十三, 迭代器模式
何为迭代器模式
定义: 提供一种方法顺序访问一个聚合对象中的各个元素, 而又不需要暴露独享的内部表示.
迭代器提供一种顺序访问聚合对象 (集合) 中元素的方法, 而无需暴露结构的底层表示和细节. 遍历集合中元素的职能从集合本身转移到迭代器对象. 迭代器定义了一个用于访问集合元素并记录当前元素的接口. 不同迭代器可以执行不同的遍历策略.
何时使用迭代器模式
需要访问组合对象的内容, 而又不暴露内部表示.
需要通过多种方式遍历组合对象.
需要提供一个统一接口, 用来遍历各种类型的组合对象.
Cocoa Touch 中的迭代器模式
NSEnumerator 从 iOS2.0 开始, 可以使用 NSEnumerator 来枚举 NSArray,NSDictionary,NSSet 对象中的元素.
基于 block 的枚举
在 iOS4 中引入了基于块的枚举(Block-Based Enumeratoration)
快速枚举(for-in)
内部枚举
NSArry 有个实例方法
-makeObjectsPerformSelector:
, 它允许客户端向数组中每个元素发送一个消息, 让其每个元素执行指定的方法.
迭代器模式 Demo(乘客买票案例) https://github.com/qingfengiOS/QFIterator
行为扩展
十四, 访问者模式
何为访问者模式
定义: 表示一个作用于某对象结构中的各元素的操作. 它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作.
何时使用访问者模式
一个复杂的对象结构包含很多其他对象, 他们有不同的接口, 这个对象的实施依赖于其具体类型的操作.
需要对一个组合结构体中的对象进行很多不相关的操作, 但是不想让这些操作 "污染" 这些对象的类, 可以将相关的操作集中起来, 定义在一个访问者类中, 并在需要在访问者中定义的操作时使用它.
定义复杂结构的类很少修改, 单经常需要向其添加新的操作.
总结
访问者模式是扩展组合结构功能的一种强有力的方式. 如果组合结构具有精心设计的基本操作, 而且结构相对稳定就可以使用访问者模式.
访问者模式 Demo(男人女人案例) https://github.com/qingfengiOS/QFVisitor.git
十五, 装饰者模式
何为装饰着模式
装饰者模式: 动态地给一个对象添加一些额外的职责. 就扩展功能来说, 装饰者模式相比生成子类更加灵活.
Component 是定义一个对象接口, 可以给这些对象动态地添加职责. ConcreteComponent 是定义了一个具体的对象, 也可以给这个对象添加一些职责. Decorator, 装饰者抽象类, 继承了 Component, 从外类来扩展 Component 类的功能, 但对于 COmponent 来说, 是无需知道 Decorator 的存在的. 至于 ConcreteDecorator 就是具体的装饰对象, 起到给 Component 添加职责的功能.
在日常开发过程中, 应该减少类继承的使用, 过多地使用类的继承会导致类数目过于庞大而变得难以维护, 而使用组合可以让我们的系统更具弹性, 更加容易修改和扩展.
何时使用装饰者模式
想要在不影响其他对象的情况下, 以动态, 透明的方式给单个对象添加职责.
想要扩展一个类的行为, 却做不到. 类定义可能被隐藏, 无法进行子类化; 或者, 对类的每个行为的扩展, 为支持各种功能组合, 将产生大量的子类.
对类的职责的扩展是可选的.
改变对象的 "外表" 和 "内容"
"外表" 变更(装饰者) | “内容” 变更(策略) |
---|---|
从外部变更 | 从内部变更 |
每个节点不知道变更 | 每个节点知道一组预定义的变更方式 |
装饰者模式 Demo(给图片加滤镜) https://github.com/qingfengiOS/QFDecorator.git
十六, 责任链模式
何为责任链模式
定义: 使多个对象都有机会处理请求, 从而避免请求的发送者和接受者之间发生耦合. 此模式将这些对象连城一天链, 并且沿这条链传递请求, 直到一个对象处理它为止.
使用责任链模式可以随意地增加或修改处理一个请求的结构, 增强了对对象指派职责的灵活性. 但是可能一个请求到了责任链的末端都得不到处理, 或者因为没有正确地配置而得不到处理, 需要考虑全面.
何时使用责任链模式
有多个对象可以处理请求, 而处理程序只有在运行时才能确定.
向一组对象发出请求, 而不想显示指定处理请求的特定处理程序.
总结
责任链模式能很好地解决大量分支判断, 有效降低了客户端调用的逻辑复杂度. 以上的 3 个设计模式, 都是在扩展对象行为的同时, 对对象进行最少修改甚至不修改. 对越来越复杂的系统扩展具有极大的借鉴意义.
责任链模式 Demo(加薪案例)
算法封装
十七, 模板方法模式
何为模板方法模式
定义: 定义一个操作中的算法骨架, 而将一些步骤延迟到子类中. 模板方法使子类可以重新定义算法的某些 y 特定步骤而不改变改算法的结构.
其基本思想是在抽象类的一个方法中定义 "标准" 算法. 这个方法的实现由子类重载实现. 这个方法被称为 "模板", 因为方法定义的算法缺少一些特定的操作. 子类重载基本操作以提供独特操作供模板方法使用.
何时使用模板方法模式
需要一次性实现算法的不变部分, 并将可变的行为留给子类实现.
子类的共同行为应该被提取出来, 放到公共类中, 以避免代码重复. 现有代码的差别应该被分离为新的操作. 然后用一个调用这些新操作的模板方法来替换这些不同的代码.
需要控制子类的扩展, 可以定义一个在特定点调用 "钩子(hook)" 操作的模板方法. 子类可以通过对钩子的操作实现在这些点扩展功能.
模板方法 vs 适配器
模板方法 | 适配器(委托)模式 |
---|---|
父类定义一个一般算法,但缺少某些特定 / 可选的信息或者算法,它通过这些缺少的信息或算法起到一个算法的 “食谱” 作用 | 适配器与预先定好的委托接口一起定义一个特定的算法 |
缺少的信息有子类通过继承实现 | 特定算法由任何对象通过组合来提供 |
模板方法模式 Demo(制作多种三明治) https://github.com/qingfengiOS/QFTemplate.git
十八, 策略模式
何为策略模式
策略模式定义了算法家族, 分别封装起来, 让他们之间可以相互替换, 此模式让算法的变化不会影响到使用算法的客户端
策略模式中的一个关键角色是策略类, 它为所有支持的相关算法声明了一个共同接口. 另外, 还有使用策略接口来实现先关算法的具体策略类. 场景 (Context) 类的对象配置有一个具体策略对象的实例, 场景对象使用策略接口调用由具体策略类定义的算法.
何时使用策略模式
一个类在其操作中使用多个条件语句来定义许多行为. 我们可以把相关的条件分支移到他们自己的策略类中.
需要算法的各种变体.
需要把复杂的, 与算法相关的数据结构暴露给客户端.
MVC 中的策略模式
模型 - 视图 - 视图控制器 (MVC) 模式中, 控制器决定视图对模型数据的显示内容和时机. 视图本身知道如何绘图, 但需要控制器告诉他要显示的内容. 一个视图如果与不同的控制器合作, 数据的输出格式可能一样, 但数据的类型和格式可能随不同控制器的不同输出而不同. 因此, 这种情况下的控制器是视图的策略. 控制器与视图之间是一种基于策略模式的关系.
总结
策略模式和装饰者模式有些类似. 装饰器从外部扩展对象的行为, 而各种策略则被封装在对象之中. 所以说装饰器改变对象的 "外表" 而策略改变对象的 "内容".
策略模式 Demo(商场打折案例) https://github.com/qingfengiOS/QFStrategy.git
十九, 命令模式
何为命令模式
命令对象封装了如何对目标执行指令的信息, 因此, 客户端或调用者不必了解目标的任何细节, 却仍可以对它执行任何已知的操作. 通过吧请求封装成对象, 客户端可以把它参数化并置入队列或者日志中, 也能支持撤销的操作. 命令对象将一个或多个动作绑定到特定的接收器. 命令模式消除了作为对象的动作和执行它的接收器之间的绑定.
定义: 将请求封装为一个对象, 从而可用不同的请求对客户端进行参数化, 队请求排队或记录请求日志, 以支持可撤销操作. 命令模式把请求一个操作的对象与知道怎么执行一个操作的对象分割开
何时使用命令模式
想让应用程序支持撤销和恢复.
想用对象参数化一个动作以执行操作, 并用不同的命令对象来代替回调函数.
想要在不同时刻对请求进行指定, 排列和执行.
想记录修改日志, 这样在系统故障时, 这些修改刻在后来重做一遍.
想让系统支持事务, 事务封装了对数据的一些列修改. 事务可以建模为命令对象.
命令模式的优点
比较容易地设计一个命令队列(waiter 的 commandList 数组)
在需要的情况下, 可以很容易地将命令记录日志(在 Waiter 的 setOrder 记录)
允许接收方命令的一方决定是否否决该命令(Cooker 类可以通知 Waiter 无货源)
可以对请求进行撤销, 修改和重做(可以修改命令的数量)
由于新加的具体命令类不影响其他类, 因此增加新的具体命令类很容易
命令模式 Demo(烧烤店的订单操作) https://github.com/qingfengiOS/QFCommand
性能与对象访问
二十, 享元模式
何为享元模式
定义: 运用共享技术有效地支持大量细粒度的对象
实现享元模式需要两个关键的组件, 通常是可共享的享元对象和保存他们的池. 某种中央对象维护这个池, 并从它返回适当的对象实例. 工厂是这一角色的理想候选, 它通过一个工厂方法返回各种类型的具体享元对象.
享元模式可以避免大量非常相似类的开销. 在程序设计中, 有时需要生成大量细粒度的类实例来表示数据. 如果能发现这些实例除了几个参数外基本相同, 有时能够大幅度地减少需要实例化类的数量. 如果能把那些参数移到类实例的外面, 在方法调用时将他们传递进来, 就可以通过共享大幅度地减少实例的数目.
何时使用享元模式
应用程序使用很多对象
在内存中保存对象会影响性能
对象的多数特有状态可以放到外部而轻量化
移除了外在状态后, 可以用较少的共享对象替换原来的那组对象
应用程序不依赖对象标识, 因为共享对象不能提供唯一标识
二十一, 代理模式
何为代理模式
定义: 为其他对象提供一种代理以控制对这个对象的访问.
代理模式的思想是使用一个基本跟实体对象行为相同的代理, 客户端可以 "透明地" 使用代理, 即不必知道所面对的只是一个代理而不是实体对象. 在 iOS 中, 使用代理来解耦合.
使用代理把 View 层的事件传递到 Controller 中
把 tableView 的 delegate 和 dataSource 实现到一个单独的 Model 或者 ViewModel 中
代理的分类
远程代理(remote proxy)
为位于不同地址空间或者网络上的对象提供本地代表
虚拟代理(virtual proxy)
需要根据需要创建重型对象
保护代理(protection proxy)
根据访问权限控制对原对象的访问
智能引用代理(samrt-reference proxy)
通过对真正对象的引用进行技术来管理内存, 也用于锁定真正对象, 让其他对象不能对其进行修改.
八, 对象状态
二十二, 备忘录模式
何为备忘录模式
定义: 在不破坏封装性的前提下, 捕捉一个对象的内部状态, 并在改对象之外保存这个状态. 这样以后就可以将该对象恢复到原先保存的状态.
备忘录模式有是三个角色: 原发器(originator), 备忘录(memento), 管理者(caretaker)
原发器(originator)
负责创建一个备忘录 Memento, 用来记录当前时刻的内部状态, 并可使用备忘录恢复内部状态. originator 可根据需要决定 Memento 存储 originator 的内部状态
备忘录(memento) 负责存储 originator 对象的内部状态, 并防止 originator 以外的对象访问备忘录.
管理者(caretaker)
负责保存好备忘录 Memento, 不能对备忘录的内容进行检查或者修改.
合适使用备忘录模式
需要保存一个对象 (或某部分) 在某一时刻的状态, 提供以后恢复到这个时刻的状态.
用于获取状态的接口会暴露实现的细节, 需要对外隐藏实现细节.
备忘录模式 Demo(游戏进度保存于恢复) https://github.com/qingfengiOS/QFMemento
来源: https://juejin.im/post/5c483849e51d4542253ffc46