中介者(mediator)模式:行为型模式
-
模式:
- 用中介对象与其他所有对象(同事对象)交互,同事对象间不直接交互;
- 同事对象不需要维护交互关系;
-
特点:
- 封装对象之间的交互,对象只在需要时通知中介者,中介者去主动处理其他对象(注意!);
- 中介者对象和同事对象直接是相互依赖的;
- 如何通知中介者:方法一在中介者中暴露通用的通知接口、同时对象在调用时会将自身当作参数传入、中介者可以获得同时对象的实例,方法二采用观察者模式;
- 封装交互;
-
广义中介者:
- 中介者和同事不再互相持有;
- 中介者可以实现为单例;
- 不需要同事元素的共同父类、不定义中介者接口;
-
优点:
- 松散耦合:使同事对象间松散耦合;
- 集中控制交互;
- 多对多关系变成双向的一对多;
-
缺点:
- 过度集中化,让中介者十分复杂、难以管理和维护;
-
使用场景:
- 一组对象之间的通信关系比较复杂;
- 一个对象引用很多对象、并直接交互,导致难以复用该对象;
代理(Proxy)模式:结构型模式
-
模式:
- 用代理代替具体的目标对象,需要时采取调用具体的目标对象;
- 实现上主要使用对象的组合和委托,也可以使用继承;
- (主要目的)控制对象访问;
-
特点:
- 代理对象相当于外部对象和被代理对象的中转;
- 分类:
-
保护代理
- 权限问题;
-
使用场景
- 需要为一个对象在不同地址空间提供局部代表的时候,可以使用远程代理;
- 需要按需创建开销较大的对象是,可以采用虚代理;
- 需要控制对原始对象的访问时,可以采用保护代理;
- 需要在访问对象时执行一些附加操作时,可以使用智能指引代理;
观察者(observer)模式:行为型模式
-
模式:
- 当状态改变时,通知所有依赖它的对象;
- 依赖对象为观察者(Observer),被观察的对象为目标(Subject),一旦目标的状态发生改变,所有注册的观察者都会得到通知、做出相应的相应;
- 触发联动;
-
特点:
- 观察者依赖目标,目标不依赖观察者;
- 又被称为发布-订阅模式;
- 先修改数据、再触发通知;
- 当心相互观察造成的死循环;
- 推模型和拉模型:
-
推模型和拉模型
- 推模型目标对象主动向观察者推送详细信息,常用于广播;
- 拉模型在通知时只传递少量信息(如目标对象自身),观察者需要具体信息后可以主动去目标对象中获取;
-
优点
- 解耦,观察者和目标之间只在抽象层面上耦合;
- 观察者和目标动态联动,可以注册、取消注册来控制观察者;
- 支持广播通信;
-
缺点:
- 广播通信可能引起无谓的操作;
-
使用场景:
- 当一个对象的操作依赖于另一个对象的操作;
- 更改一个对象时,需要连带改变其他的对象,而且不知道有多少对象需要改变;
- 当一个对象需要通知其他对象,但又希望两者松散耦合;
命令(command)模式:行为型模式
-
模式:
- 只发送命令、不关心接收者、也不关心具体如何实现;
- 用户调用 invoker,触发命令,命令和具体实现的接收者通过装配者绑定在一起;
- 使用不同的命令对象,参数化配置用户请求;
- 封装请求;
-
特点:
- 把请求封装成对象(命令对象),并定义统一执行操作的接口;
- 命令对象可以被存储、转发、记录、撤销;
- 组装者:维护命令的“虚”实现和真实实现的关系;
- 发送请求的对象和真正实现的对象是解耦的;
-
可撤销操作:
- 补偿式:反向操作;
- 存储恢复式:记录操作前状态(备忘录模式);
-
宏命令:
- 宏命令:包含多个命令的命令,是一个命令的组合;
-
队列请求:
- 依次取出命令执行;
-
日志请求
- 请求队列日志化;
-
智能命令:
- 命令对象实现命令功能,不需要接收者;
- invoker 也可以实现命令功能,即“回调”;
-
优点
- 松散的耦合:发送命令的对象和实现命令的对象是解耦的;
- 动态控制,动态地对请求进行参数化、队列化和日志化等操作;
- 复合命令;
- 更好地扩展性;
-
使用场景:
- 需要抽象出需要执行的动作,并且参数化这些对象;
- 需要在不同的时刻指定、排列和执行请求,可以封装成命令对象,并将请求队列化;
- 需要支持取消;
- 需要支持崩溃后重新执行;
- 需要事务;
迭代器模式(iterator):行为型模式
-
模式
- 用统一的方式、顺序访问内部实现不同的聚合对象;
- 具体的聚合对象应该有不同的迭代器;
- 关键思想:把聚合对象的遍历和访问从聚合对象中分离出来,放入单独的迭代器中;
- 控制访问聚合对象中的元素;
-
特点
- 迭代器延伸功能:
- 内部迭代器和外部迭代器:内部迭代可以自己控制下一个元素的步骤,客户端将操作传递给迭代器,类似回调;外部迭代器由客户端来控制迭代下一个元素,比如客户端显式地用 next;
-
优点:
- 更好的封装性:可以顺序访问一个聚合对象的内容,而不暴露聚合对象的内部表示;
- 用不同的遍历对象遍历聚合对象:将聚合对象的内容和具体的迭代算法分离开,就可以通过使用不同的迭代器实例、不同的遍历对象来遍历聚合对象;
- 简化聚合的接口:聚合对象不需要定义迭代器的接口;
- 简化客户端调用:遍历不同的聚合对象有统一的接口;
- 同一个聚合上可以有多个遍历;每个迭代器可以保持自己的遍历状态;
-
翻页功能
- 纯数据库(时间换空间):每次拉一页数据;
- 纯内存(空间换时间):每次拉所有数据,存储在内存中;
- 数据库 + 内存:每次拉几页数据,存储在内存中;
- 每次翻页迭代下一页的数据;
-
使用场景
- 访问聚合对象内容、不暴露内部表示;
- 多种遍历方式访问;
- 遍历不同的聚合对象提供统一的接口;
组合(composite)模式:结构型
-
模式
- 不区分使用单个对象和组合对象;
- 引入抽象的组件对象,作为组合对象和叶子对象的父对象,这样在操作时不用区分是组合对象还是叶子对象;
- 关键:设计一个抽象的组件类,可以代表组件对象和叶子对象,这样对于组件对象和叶子对象都只要进行统一的操作;
- 统一叶子对象和组合对象,体现了开闭原则和里氏替换原则,用父类封装不变的部分、用子类实现扩展;
-
特点:
- 通常,组合模型会组合出树形结构;
- 对象本身的递归;
- 没有层次限制;
- 通常会在组件上为对某些子对象没有意义的方法提供默认的实现;
- 要考虑索引;
-
安全性和透明性:
- 安全性:客户调用组件不会误操作,能访问的方法都是被支持的功能;
- 透明性:客户不用区分具体的类型;
-
子组件对父组件的引用
- 双向,用于删除、修改类别等场景;
-
环状引用
- 需要避免;
- 可以在组件中记录从根节点开始的路径;
- 可以在每次插入子节点时查找;
-
优点
- 构成一个统一的组合对象的类层次结构;
- 统一了组合对象和叶子对象;
- 简化了客户端调用;
- 更容易扩展;
-
缺点
- 难以限制组合中的组件类型
-
使用场景
- 表示对象的部分———整体层次结构;
- 统一地使用组合结构中的所有对象;
模板方法(template method)模式:行为型模式
-
模式
- 定义一个操作中算法的骨架,将重复或相似的代码抽离为公共的功能;
- 不同的步骤延迟到子类去实现;
- 功能:固定算法骨架,让具体的算法实现可扩展;
- 可以控制子类扩展;
- 既要约束子类的行为,又要为子类提供公共功能,所以采用了抽象类;
- 模板方法里,作为父类的模板会在需要时,调用子类相应的方法;
- 固定算法骨架;
-
模板的写法:
- 通常在模板里包含以下操作类型:
- 模板方法:定义算法骨架的方法;
- 具体的操作:在模板中直接实现某些固定步骤的方法;
- 具体的 AbstractClass 操作:辅助的公共功能;
- 原语操作:模板中定义的抽象操作,需要子类来真正实现;
- 钩子方法:模板中定义,并提供默认实现的操作,子类可以有选择地覆盖这些方法。这通常被视为可扩展的点;
- factory method:如果需要获得对象实例,可以采用工厂方法,把具体的构建对象的实现延迟到子类中;
- 通常在模板里包含以下操作类型:
-
回调实现模板模式
- 除了继承外,也可以用回调实现模板模式,回调在接口中定义的方法,调用到具体的实现类中的方法,回调方法作为接口方法的参数传入;
-
优点
- 实现代码复用,把公共部分放在模板里、实现了代码的复用;
-
缺点
- 算法骨架不容易升级;
-
使用场景
- 需要固定定义算法骨架,实现一个算法不变的部分、并把可变的行为留给子类来实现;
- 各个子类中具有公共部分,抽离可避免代码重复;
- 控制子类扩展;
策略(strategy)模式:行为型模式
-
模式
- 策略可以独立于调用的对象变化;
- 一个上下文对象负责持有算法,但不负责决定具体选用哪个算法,客户选择算法、设置到上下文对象中,并通知上下文对象执行功能,上下文对象则转调具体的算法;
- 功能:把具体的算法实现从具体的业务处理中独立出来;
- 客户端(选用具体的算法实现子类)或者上下文(把 context 当作参数传给 strategy)来选择具体的策略算法,策略可以使用接口或抽象类实现;
- 一系列算法具有公共的接口,来约束要实现的功能;
- 分离算法,选择实现;体现了开闭原则和里氏替换原则,已有的实现不需要修改就能做扩展、一系列算法可以互相替换
-
特点
- 重点在于如何组织、调用算法,从而让程序结构更灵活,具有更好的维护性和扩展性;
- if-else 就是平等的功能结构,多个 if-else 可以考虑策略模式;
- 算法具有平等性:策略算法是相同行为的不同实现;
- 客户端或者上下文来选择具体的策略算法;
- 运行时,策略是唯一的;
- 扩展灵活;
-
context 和 strategy 的关系
- 上下文封装着具体策略对象进行算法运算时所需要的数据;
- 算法实现对象可以回调上下文的方法实现一定的功能,这种情况下,上下文变相充当了策略算法实现的公共接口;
-
策略的拓展
-
优点
- 定义一系列算法,可以互相替换,这一系列算法会有公共的接口;
- 避免多重条件(if-else)语句;
- 更好的扩展性,只要增加新的策略实现类,然后再使用的地方选择新策略;
-
缺点
- 客户必须了解每种策略的不同;
- 增加列对象数目;
- 扁平的算法结构;
-
使用场景
- 出现许多相关的类,仅仅是行为有差别,策略模式可实现算法动态切换;
- 一个算法有不同实现;
- 封装算法,避免暴露与算法相关的数据结构;
- 类中定义了很多行为,并通过多个 if-else 来选择行为;