设计模式--中介者模式
定义
中介者模式定义了一个中介对象来封装一系列对象之间的交互关系。中介者使各个对象之间不需要显式地相互引用,从而使耦合性降低,而且可以独立地改变它们之间的交互行为。
角色
- 抽象中介者(Mediator):定义同事对象到中介者对象之间的接口。
- 具体中介者(ConcreteMediator):实现抽象中介者的方法,它需要知道所有的具体同事类,同时要从具体的同时类那里接收消息,并且向具体的同事类发送消息。
- 抽象同事类(Colleague):定义中介者对象的接口,它只知道中介者而不知道其它的同事对象。
- 具体同事类(ConcreteColleague):每个具体同事类都只需要知道自己的行为即可,但是它们都需要认识中介者。
类图
实现
1 | /** |
优缺点
优点
- 简化了对象之间的关系,将系统各个对象之间的关系进行封装,将各个同事类进行解耦,使得系统变为松耦合
- 提供系统的灵活性,使得各个同事对象独立而易于复用
缺点
- 中介者角色承担了较多的责任,所以一旦这个中介者对象出了问题,整个系统将会受到重大的影响
- 新增加一个同事类时,不得不去修改抽象终结者类和具体中介者类(可以使用观察者模式和状态模式来解决这个问题)
适用场景
- 一组定义良好的对象,现在要进行复杂的通信
- 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类
模式应用
设计模式--桥接模式
定义
桥接模式将抽象部分与它的实现部分分离,使它们都可以独立地变化。
角色
- 抽象类(Abstraction)
- 扩充抽象类(RefinedAbstraction)
- 实现类接口(Implementor)
- 具体实现类(ConcreteImplementor)
类图
实现
1 | /** |
模式分析
理解桥接模式,重点需要理解如何将抽象化(Abstraction)与实现化(Implementation)解耦,使得二者可以独立地变化。
- 抽象化:抽象化就是忽略一些信息,把不同的实体当作同样的实体对待。在面向对象中,将对象的共同性质抽取出来形成类的过程即为抽象化的过程。
- 实现化:针对抽象化给出的具体实现,就是实现化,抽象化与实现化是一对互逆的概念,实现化产生的对象比抽象化更具体,是对抽象化事物的进一步具体化的产物。
- 脱耦:脱耦就是将抽象化与实现化之间的耦合解脱开,或者说是将它们之间的强关联该换为弱关联,将两个角色之间的继承关系改为关联关系。桥接模式中所说的脱耦,就是指在一个软件系统的抽象化和实现化之间使用关联关系(组合或者聚合关系)而不是继承关系,从而使两者可以相对独立的变化,这就是桥接模式的用意。
优缺点
优点
- 分离抽象接口及其实现部分
- 桥接模式有时类似于多继承方案,但是多继承方案违背了类的单一职责原则,复用性比较差,而且多继承结构中类的个数非常庞大
- 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统
- 实现细节对客户透明,可以对客户隐藏实现细节
缺点
- 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性
适用场景
- 系统需要在构建的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承关系,通过桥接模式可以使它们在抽象层建立一个关联关系
- 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展
模式应用
Java虚拟机
JDBC驱动
使用JDBC驱动程序的应用系统就是抽象角色,而所使用的数据库是实现角色。一个JDBC驱动程序可以动态地将一个特定类型的数据库与一个Java应用程序绑在一起,从而实现抽象角色与实现角色的动态耦合。
设计模式--组合模式
定义
组合模式允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。
角色
- 抽象构件(Component):其主要作用是为树枝构件和树叶构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中,抽象构件还声明访问和管理子类的接口;在安全式的组合模式中,不声明访问和管理子类的接口,管理工作由树枝构件完成。
- 树枝构件(Composite):是组合中的分支节点对象,它有子节点。它实现了抽象构件角色中声明的接口,它的主要作用是存储和管理子部件。
- 树叶构件(Leaf):是组合中的叶节点对象,它没有子节点,用于实现抽象构件角色中声明的公共接口。
类图
实现
1 | /** |
优缺点
优点
- 组合模式使得客户端代码可以一致的处理对象和对象容器,无需关心处理的是单个对象,还是组合的对象容器
- 将客户对象与复杂的对象容器结构解耦
- 可以更容易地往组合对象中加入新的构件
缺点
- 使得设计更加复杂,客户端需要花更多时间理清类之间的层次关系
适用场景
- 当想表达对象的部分-整体的层次结构时
- 希望用户忽略组合对象与单个对象不同,用户将统一地使用组合结构中的所有对象时
模式应用
Mybatis
1 | public interface SqlNode { |
识别组合模式的一个要点:实现了一个接口,又聚合了这个接口的集合,那么该类很有可能是组合模式中的组合对象。
设计模式--状态模式
定义
状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
角色
- 环境(Context):也称上下文,它定义了客户感兴趣的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
- 抽象状态(State):定义一个接口,用以封装环境对象中的特定状态所对应的行为。
- 具体状态(Concrete State):实现抽象状态所对应的行为。
类图
实现
1 | /** |
优缺点
优点
- 封装了转换规则
- 枚举可能的状态,在枚举状态之前需要确定状态种类
- 将所有与某个状态有关的行为封装在一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为
- 允许状态转换逻辑与状态对象合为一体,而不是某一个巨大的条件语句块
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数
缺点
- 状态模式的使用必然会增加系统类和对象的个数
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱
- 状态模式对开闭原则的支持并不太友好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需要修改对应的源代码
适用场景
- 行为随状态改变而改变的场景
- 条件、分支语句的替代者
模式应用
设计模式--监听器模式
定义
监听器模式是观察者模式的一种实现:事件源经过事件的封装传给监听器,当事件源触发事件后,监听器接收到事件对象可以回调事件的方法。
角色
- 事件源:提供订阅与取消监听器的方法,并负责维护监听者列表,发送事件给监听者。
- 事件对象:事件类型,同时包装事件源。
- 监听器:每一个监听器实现接口来接收事件,并负责从事件源订阅与取消订阅。
类图
实现
1 | /** |
模式应用
Spring Boot
在Spring Boot启动过程中,会组播一系列事件,这些事件的触发及响应就用到了监听器模式。
设计模式--观察者模式
定义
观察者模式定义了对象之间的一对多依赖,这样一来,当对象改变状态时,它的所有依赖者都会收到通知并自动更新。
角色
- 抽象主题(Subject):它把所有观察者对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
- 具体主题(Concrete Subject):将有关状态存入具体观察者对象;在具体主题内部状态改变时,给所有登记过的观察者发出通知。
- 抽象观察者(Observer):为所有的具体观察者定义一个接口,在得到主题通知时更新自己。
- 具体观察者(Concrete Observer):实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题状态协调。
类图
实现
1 | public class ConcreteObservable extends Observable { |
优缺点
优点
- 观察者模式解除了主题和具体观察者的耦合,让耦合的双方都依赖于抽象,而不是依赖于具体
缺点
- 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间
- 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环依赖,可能导致系统崩溃。
- 观察者模式没有相应的机制让观察者知道所观察的目标对象时怎么发生变化的,而仅仅只是知道观察者发生了变化。
适用场景
- 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象…,可以使用观察者模式创建一种链式触发机制
模式应用
Java Swing
Spring事件发布
Spring事件发布使用的是观察者模式的另一种形态:监听器模式,它们都是基于事件驱动模型。
设计模式--装饰者模式
定义
装饰者模式动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
角色
- 抽象构件(Component):定义一个接口,来规范准备附加功能的类。
- 具体构件(Concrete Component):将要被附加功能的类,实现抽象构件角色接口。
- 抽象装饰者(Decorator):持有对具体构件的引用,并定义与抽象构件角色一致的接口。
- 具体装饰者(Concrete Decorator):实现抽象装饰者角色,负责为具体构件添加额外功能。
类图
实现
1 | public abstract class Component { |
优缺点
优点
- 装饰者和被装饰者可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态地扩展一个实现类的功能
缺点
- 多层装饰比较复杂
使用场景
- 动态扩展类的功能
模式应用
JAVA IO
Mybatis
Mybatis的二级缓存模块中,使用了装饰者模式的变体,其中将Decorator接口和Component接口合并为一个Component接口,UML类图如下:
由类图可知,PerpetualCache
扮演着Concrete Component(具体组件实现类)的角色,其余的都是装饰类。
实际应用
电商系统中,经常会有限时折扣、红包、抵扣卷以及特殊抵扣金等商品优惠策略,要实现这些组合优惠的功能,最快、最普遍的实现方式就是通过大量的if-else的方式来实现,但这种方式包含了大量的逻辑判断,致使其他开发人员很难读懂业务,并且一旦有新的优惠策略或价格组合策略出现,就需要修改代码逻辑,这时就可以使用装饰者模式,其相互独立、自由组合以及方便动态扩展功能的特性,可以很好的解决if-else方式的弊端。
设计模式--原型模式
定义
原型模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节。工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。
要素
- 实现
Cloneable
接口 - 重写Object的
clone
方法
类图
实现
1 | public abstract class Subject implements Cloneable { |
优缺点
优点
- 性能提高
- 逃避构造函数的约束
缺点
- 必须实现
Cloneable
接口 - 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不是很容易,特别当一个类引用不支持串行化的间接对象,或者含有循环结构的时候
使用场景
- 资源优化场景
- 初始化需要消耗非常多的资源,包括数据、硬件资源等
- 初始化需要非常繁琐的数据准备或访问权限
- 一个对象多个修改者的场景
模式应用
Spring中bean默认都是单例的,用了私有全局变量,若不想影响下次注入或每次上下文获取bean,就需要用到原型模式,我们可以通过以下注解来实现:@Scope(“prototype”),或@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)。
实际应用
实际应用中,需要注意浅拷贝带来的问题,要想深拷贝,需要对象里的对象属性也实现Cloneable
接口,同时在clone
方法中调用对象属性的clone
方法。
设计模式--责任链模式
定义
责任链模式使多个对象都有处理请求的机会,从而避免了请求的发送者和接收者之间的耦合关系。
角色
- 抽象处理者(Handler):该角色对请求进行抽象,并定义一个方法来设定和返回对下一个处理者的引用。
- 具体处理者(Concrete Handler):该角色收到请求后,可以选择将请求处理掉,或者将请求传给下一个处理者。由于具体处理者持有对下一个处理者的引用,因此,如果需要,处理者可以访问下一个处理者。
类图
实现
1 | public abstract class Handler { |
优缺点
优点
- 降低耦合度。它将请求的发送者和接收者解耦
- 简化了对象,使得对象不需要知道链的结构
- 增强给对象指派职责的灵活性。通过改变链的成员或调动它们的次序,允许动态的新增或删除责任
缺点
- 不能保证请求一定被接收
- 系统性能将受到影响,而且在进行代码调试时不太方便,可能会造成循环调用
适用场景
- 有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定
- 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求
- 可动态指定一组对象处理请求
模式应用
Spring Security是基于Filter实现安全访问的,它里面包含了大量的filters,这些filters的运行机制中就用到了责任链模式。
不过它不是标准的责任链模式,因为责任链太长或者每条链判断处理的时间太长会影响性能,所以它在实现中并没有不断地去设置next filter,而是将filters定义在数组中,然后通过递增数组下标来访问下一个filter。
1 | public class FilterChainProxy extends GenericFilterBean { |