《设计模式》-05-GoF模式-行为型模式(下)
文章目录
7. 观察者模式(Observer)
指当目标对象状态发生变化后,对状态变化事件进行响应或处理的对象。
诠释:
观察者模式有时称为“发布者—订阅者模式”(Publisher-Subscriber Pattern),在事件处理的场景中,也叫监听器(Listener)模式。
观察者模式定义了依赖对象和被依赖对象之间的多对一关系,当被依赖对象(或称目标对象)的状态发生改变时,依赖对象能够及时地收到通知并做出响应。
7.1 使用场景
(1)当目标对象的状态发生变化时,需要将状态变化事件通知到其他依赖对象,但并不知道依赖对象的具体数量或类型。
(2)抽象层中具有依赖关系的对象需要独立封装,以便复用或扩展。
7.2 类结构
由于Subject聚合的是Observer抽象类型定义的对象,所以Observer实现类的变化不会影响Subject,同样,Subject也可以自由扩展子类。
当Subject状态发生变化时,使用notifyObserver()方法将新状态(可以是消息或事件)通知所有Observer对象,Observer对象收到通知后,根据业务规则选择响应或不响应。
7.3 使用观察者
在COS系统中,客户可以订阅平台优惠信息,订阅方式是邮件或短信,将来会增加新的订阅方式。当COS有新的优惠信息时,需要及时通知订阅的客户。
8. 状态模式(State)
用于封装上下文对象的特定状态行为,使得上下文对象在内部状态改变时能够改变其自身的行为。
8.1 使用场景
(1)上下文对象的行为依赖其内部的状态,而状态在运行时会发生变化。
(2)需要消除上下文对象中针对状态判断的逻辑分支语句。
8.2 类结构
- Context为上下文对象类型,持有State抽象类型的对象引用;
- State是封装状态行为handle()的抽象类型,具体的状态行为由子类实现;
- StateA、StateB和StateC是具体的状态行为实现类。当需要添加新类型的状态行为时,只需实现State的新状态子类即可完成扩展。
由于Context类型对象行为behavior()的具体表示由内部的状态对象决定;因此,可以消除behavior()内部状态判断的分支语句。
- 时序图:
客户端调用上下文Context类型对象内部状态对应的行为behavior()时,上下文对象会将客户请求委托给State类型的状态对象处理。
8.3 使用状态模式
COS中的菜品订单具有正在下单(Placing)、已接受(Accepted)、已准备(Prepared)、已完成(Finished)等状态,每种状态下的菜品订单“取消”行为并不一样
- 当订单的状态为Placing、Accepted时,如果客户取消订单,COS会将该订单状态置为“已取消(Canceled)”,客户获得订单100%退款
- 当订单状态为Prepared时,客户取消订单,COS会将该订单状态置为Canceled,客户获得订单30%退款;
- 当订单状态为Delivering、Delivered、Finished时,订单的“取消”行为不可用。
为了保证代码的稳定性,开发人员并不想使用分支语句构造菜品订单对象的状态行为。
9. 策略模式(Strategy)
用于封装一组算法中单个算法的对象,这些策略可以相互替换,使得单个算法的变化不影响使用它的客户端。
9.1 使用场景
(1)算法需要实现不同的、可替换的变体。
(2)向使用算法的客户类(或上下文类)屏蔽算法内部的数据结构。
(3)客户类(或上下文类)定义了一组可相互替换的行为,需要消除调用这组行为的分支语句。
(4)一组类仅在行为上不同,但开发人员不想通过子类扩展的方式实现行为多态。
9.2 类结构
- Context是算法策略的上下文类,也是使用策略对象的客户类;
- Strategy接口定义了算法的行为behavior(),实现类ConcreteStrategyA、ConcreteStrategyB等实现了具体的算法。
- 值得注意的是,执行策略类算法所需要的数据是从上下文中获得的。因此,Context上下文对象在使用策略对象时,需要将策略行为所需要的所有数据传入策略对象中,或将自己传入并提供算法行为所需数据的访问接口(图示结构采用将上下文对象作为方法参数传入策略行为中)。
时序图:
客户端在使用上下文对象的服务时,向其传入具体的策略对象引用;上下文对象收到客户端请求后,委托策略对象完成业务请求处理。
6.3 示例
客户在浏览COS菜单时,可以选择按价格或名称进行排序(升序或降序)浏览。在COS的升级版本中,会添加新的排序方式(按热量、时令等排序)。
10.模板方法(Template Method)
用来定义算法的框架,将算法中的可变步骤定义为抽象方法,指定子类实现或重写。
诠释:
模板方法定义了算法的不变部分(包括算法过程、执行顺序、输入/输出等),而将可变部分(一般是算法过程中的某些子步骤)延迟到子类中具体实现。优势:
- 避免代码冗余。
算法的不可变部分由模板方法定义,被所有子类复用,可以避免子类重复定义相同的代码。- 提高代码稳定性。
算法的可变部分是不稳定的,将不稳定的子步骤定义为抽象方法,模板方法依赖于抽象方法,能保证算法框架的稳定性。
10.1 使用场景
(1)当算法中含有可变步骤和不可变步骤的时候,让子类决定可变步骤的具体实现。
(2)当多个类中含有公共业务行为,开发人员想要避免重复定义冗余的代码。
(3)想要控制子类的扩展行为,只允许子类实现特定的扩展点。
10.2 类结构
抽象类AbstractClass定义了模板方法templateMethod(),模板方法是算法行为(或业务过程)的框架,算法框架中包含不可变步骤invariantStep()和可变步骤variantStep()。
- templateMethod()使用了final修饰词,不允许子类重定义它。
- invariantStep()可见性为private—子类不可见,也不能重定义;
- variantStep()可见性是protected,允许子类继承和重定义。
- 因此,父类AbstractClass指定了子类ConcreteClass只能重写扩展点variantStep()。
当AbstractClass类型的对象向客户端提供templateMethod()服务时,由客户端选择或指定具体的ConcreteClass实例。
时序图:
10.3 示例
COS客户支付订单的步骤是核对并确认订单、支付订单、生成回执。支付订单的方式可以是卡支付或工资抵扣,客户可以自由选择支付方式,而COS要确保支付流程一致。
11. 访问者模式(Visitor)
用于封装施加在聚合体中聚合元素的操作(或算法),从而使该操作(或算法)从聚合对象中分离出来,在不对聚合对象产生影响的前提下实现自由扩展。
对于由若干类型元素组成的聚合对象进行元素操作时,需要根据元素类型施加不同操作。如果将聚合元素的操作封装在聚合对象中,则代码具有两大缺陷:
- 代码复杂度大
因为对不同类型的聚合元素施加不同的操作,聚合对象不仅要实现聚合元素的管理,还需定义不同元素的操作,导致了聚合对象的代码复杂度增大- 稳定性差
聚合对象封装了施加在不同聚合元素上的操作,当需要对元素操作进行扩展时,必须修改聚合对象的业务逻辑,才能完成代码扩展。
11.1 使用场景
(1)目标聚合对象包含不同的聚合元素类型,开发人员需要实现针对不同的聚合元素类型施加不同的业务操作或算法行为。
(2)目标聚合对象结构稳定,但针对聚合元素的操作需要实现不同的扩展。
(3)有多个单一且不相关的操作施加在聚合元素上,但不想“污染”聚合元素类的代码。
11.2 类结构
- ObjectStructure是聚合体类型,由Element类型的聚合元素聚合而成;
- Element是聚合元素的抽象类,有不同的实现类型ElementA、ElementB等;
- 接口Visitor定义了施加在Element上的操作visit(),针对不同Element实现类型定义了不同的visit()重载方法。
由于Visitor封装了针对Element实现类型的操作行为,所以,当需要扩展新的操作时,定义Visitor新的实现类即可。
时序图
Client创建和使用具体的Visitor对象,通过ObjectStructure定义的接口service()对聚合元素施加操作。
- 当ObjectStructure类型的对象收到客户端请求后,将携带Visitor实例的请求通过accept()方法分发给对应的聚合元素。
- 聚合元素对象收到ObjectStructure分发的请求后,通过visit()方法再次将请求分发至Visitor实例,并将自身作为方法参数传入;
- Visitor对象收到聚合元素提交的visit()请求后,根据该请求携带的聚合元素实例施加相对应的重载操作行为,并在需要时访问聚合元素提供的接口operation()。