根据范围,即模式主要是处理类之间的关系还是处理对象之间的关系,可分为类模式和对象模式两种:
- 类模式处理类和子类之间的关系,这些关系通过继承建立,在编译时刻就被确定下来,是一种静态关系
- 对象模式处理对象间的关系,这些关系在运行时变化,更具动态性
- 将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。
- 命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式
- 将请求发送者和接收者完全解耦
- 发送者与接收者之间没有直接引用关系
- 发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求
- 命令模式的本质是对请求进行封装
- 一个请求对应于一个命令,将发出命令的责任和执行命令的责任分开
用一个电视遥控器的命令来诠释这个模式
- Command: 抽象命令类
- ConcreteCommand: 具体命令类
- Invoker: 调用者
- Receiver: 接收者
- 降低系统的耦合度
- 新的命令可以很容易地加入到系统中,符合开闭原则
- 可以比较容易地设计一个命令队列或宏命令(组合命令)
- 为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计和实现方案
使用命令模式可能会导致某些系统有过多的具体命令类(针对每一个对请求接收者的调用操作都需要设计一个具体命令类)
- 提供一种方法来访问聚合对象,而不用暴露这个对象的内部表示。
- 其别名为游标(Cursor)
- 迭代器模式是一种对象行为型模式
- 聚合对象的两个职责:存储数据,聚合对象的基本职责。遍历数据,既是可变化的,又是可分离的
- 将遍历数据的行为从聚合对象中分离出来,封装在迭代器对象中
- 由迭代器来提供遍历聚合对象内部数据的行为,简化聚合对象的设计,更符合单一职责原则
使用一个电视台切换的例子
- Iterator: 抽象迭代器
- ConcreteIterator: 具体迭代器
- Aggregate: 抽象聚合类
- ConcreteAggregate: 具体聚合类
- 支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式
- 简化了聚合类
- 由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,符合开闭原则
- 在增加新的聚合类时需要对应地增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性
- 抽象迭代器的设计难度较大,需要充分考虑到系统将来的扩展。在自定义迭代器时,创建一个考虑全面的抽象迭代器并不是一件很容易的事情
- 定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。
- 观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式
- 观察者模式是一种对象行为型模式
我们维护一个观察字符串的列表类,他们观察到传入了字符串做相应的动作
/**
* @author lhj
* @create 2022/4/13 23:36
*/
public class Client {
public static void main(String[] args) {
MySubject subject = new TextEditingArea();
MyObserver obs1, obs2, obs3;
obs1 = new ObserverOne();
obs2 = new ObserverTwo();
obs3 = new ObserverThree();
subject.attach(obs1);
subject.attach(obs2);
subject.attach(obs3);
subject.show("i i am am a a very very beautiful beautiful girl girl");
}
}
/**
* @author lhj
* @create 2022/4/9 21:29
* 抽象目标类
*/
public abstract class MySubject {
protected ArrayList observers = new ArrayList();
//注册方法
public void attach(MyObserver observer){
observers.add(observer);
}
//注销方法
public void detach(MyObserver observer){
observers.remove(observer);
}
//抽象通知方法
public abstract void show(String str);
}
/**
* @author lhj
* @create 2022/4/9 21:28
* 抽象观察者类
*/
public interface MyObserver {
void response(String str);
}
/**
* @author lhj
* @create 2022/4/13 23:45
*
*/
public class TextEditingArea extends MySubject{
@Override
public void show(String str) {
System.out.println("开始分析字符串");
System.out.println("----------------------");
for (Object observer : observers) {
((MyObserver) observer).response(str);
}
}
}
/**
* @author lhj
* @create 2022/4/13 23:55
* 这个文本统计区(被观察者)负责显示可编辑文本区中出现的单次总数量和字符总数量
*/
public class ObserverOne implements MyObserver{
@Override
public void response(String str) {
String[] s = str.split(" ");
System.out.println("单词数量为:"+s.length);
char[] chars = str.toCharArray();
System.out.println("字符数量为:"+chars.length);
}
}
/**
* @author lhj
* @create 2022/4/13 23:55
* 这个文本统计区(被观察者)负责可编辑区出现的单次(去重后按照字典序排序)
*/
public class ObserverTwo implements MyObserver{
@Override
public void response(String str) {
String[] s = str.split(" ");
List<String> list = new ArrayList<>(Arrays.asList(s));
for(int i = 0; i < list.size();i++) {
for (int j = i + 1; j < list.size(); j++){
if(list.get(i).equals(list.get(j))) {
list.remove(j);
continue;
}
}
}
Collections.sort(list);
System.out.println(list);
}
}
/**
* @author lhj
* @create 2022/4/13 23:55
* 这个文本统计区(被观察者)负责按照出现频次降序显示可编辑区中出现的单词以及每个单个单词出现的次数
* * 例如:hello:5
*/
public class ObserverThree implements MyObserver{
@Override
public void response(String str) {
String[] s = str.split(" ");
Map<String, Integer> map = new HashMap<>();
for (String s1 : s) {
if(map.containsKey(s1))
map.put(s1, map.get(s1) + 1);
else
map.put(s1, 1);
}
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey()+":"+entry.getValue());
}
}
}
- 可以实现表示层和数据逻辑层的分离
- 在观察目标和观察者之间建立一个抽象的耦合
- 符合开闭原则,增加新的具体支持广播通信,简化了一对多系统设计的难度
- 观察者无须修改原有系统代码,在具体观察者与观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便
- 将所有的观察者都通知到会花费很多时间
- 如果存在循环依赖时可能导致系统崩溃
- 没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而只是知道观察目标发生了变化
- 定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。
- 策略模式让算法独立于使用它的客户而变化
- 策略模式是一种对象行为型模式
使用策略模式会节省大量代码,提高代码可读性,增强逻辑性
- 例如我们写一个奖励的服务,根据奖励类型返回响应的响应,未使用策略模式的就很臃肿
// 奖励服务
class RewardService {
// 外部服务
private WaimaiService waimaiService;
private HotelService hotelService;
private FoodService foodService;
// 使用对入参的条件判断进行发奖
public void issueReward(String rewardType, Object ... params) {
if ("Waimai".equals(rewardType)) {
WaimaiRequest request = new WaimaiRequest();
// 构建入参
request.setWaimaiReq(params);
waimaiService.issueWaimai(request);
} else if ("Hotel".equals(rewardType)) {
HotelRequest request = new HotelRequest();
request.addHotelReq(params);
hotelService.sendPrize(request);
} else if ("Food".equals(rewardType)) {
FoodRequest request = new FoodRequest(params);
foodService.getCoupon(request);
} else {
throw new IllegalArgumentException("rewardType error!");
}
}
}
优化后:
// 策略接口
interface Strategy {
void issue(Object ... params);
}
// 外卖策略
class Waimai implements Strategy {
private WaimaiService waimaiService;
@Override
public void issue(Object... params) {
WaimaiRequest request = new WaimaiRequest();
// 构建入参
request.setWaimaiReq(params);
waimaiService.issueWaimai(request);
}
}
// 酒旅策略
class Hotel implements Strategy {
private HotelService hotelService;
@Override
public void issue(Object... params) {
HotelRequest request = new HotelRequest();
request.addHotelReq(params);
hotelService.sendPrize(request);
}
}
// 美食策略
class Food implements Strategy {
private FoodService foodService;
@Override
public void issue(Object... params) {
FoodRequest request = new FoodRequest(params);
foodService.payCoupon(request);
}
}
- 提供了对开闭原则的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为
- 提供了管理相关的算法族的办法
- 提供了一种可以替换继承关系的办法
- 可以避免多重条件选择语句
- 提供了一种算法的复用机制,不同的环境类可以方便地复用策略类
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类
- 将造成系统产生很多具体策略类
- 无法同时在客户端使用多个策略类
- 模板方法模式(Template Method Pattern):定义一个操作中算法的框架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤
- 是一种基于继承的代码复用技术
- 将一些复杂流程的实现步骤封装在一系列基本方法中
- 在抽象父类中提供一个称之为模板方法的方法来定义这些基本方法的执行次序,而通过其子类来覆盖某些步骤,从而使得相同的算法框架可以有不同的执行结果
UML类图
钩子方法
代码实例
在AQS的相关类中,大量使用了这种设计模式
/**
* @author lhj
* @create 2022/5/24 17:08
*/
public class MyLock{
private Sync sync = new Sync();
private static class Sync extends AbstractQueuedSynchronizer{
@Override
protected boolean tryAcquire(int arg) {
return compareAndSetState(0, 1);
}
@Override
protected boolean tryRelease(int arg) {
setState(0);
return true;
}
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
}
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
}
- 在父类中形式化地定义一个算法,而由它的子类来实现细节的处理,在子类实现详细的处理算法时并不会改变算法中步骤的执行次序
- 提取了类库中的公共行为,将公共行为放在父类中,而通过其子类来实现不同的行为
- 可实现一种反向控制结构,通过子类覆盖父类的钩子方法来决定某一特定步骤是否需要执行
- 更换和增加新的子类很方便,符合单一职责原则和开闭原则
- 需要为每一个基本方法的不同实现提供一个子类,如果父类中可变的基本方法太多,将会导致类的个数增加,系统会更加庞大,设计也更加抽象
- 职责链模式(Chain of Responsibility Pattern):避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求。将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
- 将请求的处理者组织成一条链,并让请求沿着链传递,由链上的处理者对请求进行相应的处理
- 客户端无须关心请求的处理细节以及请求的传递,只需将请求发送到链上,将请求的发送者和请求的处理者解耦
- 对象行为型模式
结构UML类图
代码实例
对于职责链模式在spring中的拦截链就是用此设计模式,我们模拟前置通知和后置通知来看看组成
spring拦截链的具体解释
//统一规范,定义一个Advice通知接口(拦截器)
public interface IMyInterceptor {
void adviceMethod(ITargetService targetService);
}
//前置通知
@Service
public class BeforeInterceptorImpl implements IMyInterceptor {
/**
* 前置通知的方法
* @param targetService
*/
@Override
public void adviceMethod(ITargetService targetService) {
System.out.println("执行前置通知...");
//调用执行目标方法的业务逻辑:
targetService.targetMethod();
}
}
//后置通知
@Service
public class AfterInterceptorImpl implements IMyInterceptor {
/**
* 后置通知的方法
* @param targetService
*/
@Override
public void adviceMethod(ITargetService targetService) {
//调用执行目标方法的业务逻辑:
targetService.targetMethod();
System.out.println("执行后置通知...");
}
}
//改造目标方法targetMethod,将多个通知织入目标方法,并以责任链模式的方式进行织入:
@Service
public class TargetServiceImpl implements ITargetService {
private List<IMyInterceptor> myInterceptorList; //拦截器集合,即拦截器链
//实例化目标类对象的时候,注入属性满足依赖;
public TargetServiceImpl(List<IMyInterceptor> myInterceptorList) {
this.myInterceptorList = myInterceptorList;
}
private int currentInterceptorIndex = -1; //该指针驱动责任链条向前执行
/**
* 目标方法,切入点
*/
@Override
public void targetMethod() {
if (currentInterceptorIndex == this.myInterceptorList.size() - 1) {
System.out.println("具体业务方法,正在执行具体业务逻辑...");
return;
}
//获取拦截器链中当前的拦截器
IMyInterceptor myInterceptor = this.myInterceptorList.get(++this.currentInterceptorIndex);
//执行当前拦截器的Advice通知:
myInterceptor.adviceMethod(this);
}
public TargetServiceImpl() { }
}
/****************************** 需要调用的目标方法 *******************************************/
//目标结构
public interface ITargetService {
void targetMethod(); //目标方法--切入点
}
//目标类
@Service
public class TargetServiceImpl implements ITargetService {
/**
* 目标方法,切入点
*/
@Override
public void targetMethod() {
System.out.println("具体业务方法,正在执行具体业务逻辑...");
}
}
- 使得一个对象无须知道是其他哪一个对象处理其请求,降低了系统的耦合度
- 可简化对象之间的相互连接
- 给对象职责的分配带来更多的灵活性
- 增加一个新的具体请求处理者时无须修改原有系统的代码,只需要在客户端重新建链即可
- 不能保证请求一定会被处理
- 对于比较长的职责链,系统性能将受到一定影响,在进行代码调试时不太方便
- 如果建链不当,可能会造成循环调用,将导致系统陷入死循环
- 有多个对象可以处理同一个请求,具体哪个对象处理该请求待运行时刻再确定
- 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求
- 可动态指定一组对象处理请求
- 在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。
- 它是一种对象行为型模式,其别名为
Token
- 提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤
- 当前在很多软件所提供的撤销
(Undo)
操作中就使用了备忘录模式- 除了
Originator
类,不允许其他类来调用备忘录类Memento
的构造函数与相关方法- 如果允许其他类调用
setState()
等方法,将导致在备忘录中保存的历史状态发生改变,通过撤销操作所恢复的状态就不再是真实的历史状态,备忘录模式也就失去了本身的意义- 理想的情况是只允许生成该备忘录的原发器访问备忘录的内部状态
- 提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤
- 实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示,不会被其他代码所改动
- 资源消耗过大,如果需要保存的原发器类的成员变量太多,就不可避免地需要占用大量的存储空间,每保存一次对象的状态都需要消耗一定的系统资源
- 状态模式(State Pattern) :允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
- 其别名为状态对象(Objects for States),状态模式是一种对象行为型模式
模式结构
代码演示
未使用状态模式:
if(state=="空闲") {
if(预订房间) {
预订操作;
state="已预订";
}
else if(住进房间) {
入住操作;
state="已入住";
}
}
else if(state=="已预订") {
if(住进房间) {
入住操作;
state="已入住";
}
else if(取消预订) {
取消操作;
state="空闲";
}
}
重构后
//重构之后的“空闲状态类”示例代码
if(预订房间)
{
预订操作;
context.setState(new 已预订状态类());
}
else if(住进房间)
{
入住操作;
context.setState(new 已入住状态类());
}
- 封装了状态的转换规则,可以对状态转换代码进行集中管理,而不是分散在一个个业务方法中
- 将所有与某个状态有关的行为放到一个类中,只需要注入一个不同的状态对象即可使环境对象拥有不同的行为
- 允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块,可以避免使用庞大的条件语句来将业务方法和状态转换代码交织在一起
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数
- 会增加系统中类和对象的个数,导致系统运行开销增大
- 结构与实现都较为复杂,如果使用不当将导致程序结构和代码混乱,增加系统设计的难度
- 对开闭原则的支持并不太好,增加新的状态类需要修改负责状态转换的源代码,否则无法转换到新增状态;而且修改某个状态类的行为也需要修改对应类的源代码
- 中介者模式(Mediator Pattern)定义:用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
- 中介者模式又称为调停者模式,它是一种对象行为型模式
我们想象使用即时通讯软件例如QQ,我们聊天的场景大致有两个,一是与别的好友聊天,系统维护着多对多的关系,二是在群里水群跟很多好友聊天,在群里聊天通过引入中介者的分享文件发送消息,任何人都能收到,极大的降低了系统的耦合性
中介者的职责:中转作用和协调作用
模式结构
优点
- 简化了对象之间的交互,它用中介者和同事的一对多交互代替了原来同事之间的多对多交互,将原本难以理解的网状结构转换成相对简单的星型结构
- 可将各同事对象解耦
- 可以减少子类生成,中介者模式将原本分布于多个对象间的行为集中在一起,改变这些行为只需生成新的中介者子类即可,这使得各个同事类可被重用,无须直接对同事类进行扩展
- 在具体中介者类中包含了大量的同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护
- 解释器模式(Interpreter Pattern) :定义一个语言的文法,并且建立一个解释器来解释该语言中的句子。
- 此处,“语言”是指使用规定格式和语法的代码
- 它是一种类行为型模式
- 例如,Java的执行器如何对 class文件进行执行,先校验魔数,再校验版本号,然后是常量池的大小等等。
模式结构
优点
- 易于改变和扩展文法
- 可以方便地实现一个简单的语言
- 增加新的解释表达式较为方便
- 对于复杂文法难以维护
- 执行效率较低
- 对象结构中存储了多种不同类型的对象信息
- 对同一对象结构中的元素的操作方式并不唯一,可能需要提供多种不同的处理方式
- 还有可能需要增加新的处理方式
- 以不同的方式操作复杂对象结构->访问者模式
- Visitor(抽象访问者)
- ConcreteVisitor(具体访问者)
- Element(抽象元素)
- ConcreteElement(具体元素)
- ObjectStructure(对象结构)
- 增加新的访问操作很方便
- 将有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个的元素类中,类的职责更加清晰
- 让用户能够在不修改现有元素类层次结构的情况下,定义作用于该层次结构的操作
- 增加新的元素类很困难
- 破坏了对象的封装性