【重温设计模式】行为模式之职责链模式,状态模式,迭代器模式
一、职责链模式
1、为什么使用职责链模式
- 定义:将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。
- 分类:职责链模式有两种常用的实现。一种是使用链表来存储处理器,另一种是使用数组来存储处理器
2、应用设计模式的目的
- 应用设计模式主要是为了应对代码的复杂性,让其满足开闭原则,提高代码的扩展性。
类似职责链模式的应用,是要把代码分为两类:框架代码和客户端代码,假设敏感词过滤框架并不是我们开发维护的,而是我们引入的一个第三方框架,我们要扩展一个新的过滤算法,不可能直接去修改框架的源码。这个时候,利用职责链模式就能达到开篇所说的,在不修改框架源码的情况下,基于职责链模式提供的扩展点,来扩展新的功能。换句话说,我们在框架这个代码范围内实现了开闭原则。
3、职责链模式应用场景
- 过滤器:敏感词过滤器,
- 拦截器:限流,熔断
4、职责链模式应用场景
(1)spring的动态代理的增强,就使用了责任链模式(数组类型的责任链模式)
- 入口类:org.springframework.aop.framework.JdkDynamicAopProxy 在客户端调用某个方法时,会先调用java.lang.reflect.InvocationHandler接口定义的Object invoke(Object proxy, Method method, Object[] args)
- 在上述实现方法中,会将该方法需要增强的类组装成职责链对象,依次调用。会将Proxy,Method,args,职责链集合封装到org.springframework.aop.framework.ReflectiveMethodInvocation类中
- 依次执行职责链的功能,再执行被代理的方法(该职责链是在方法执行的前后加入了逻辑)
二、状态模式
1、为什么使用状态模式
- 有限状态机:英文翻译是 Finite State Machine,缩写为 FSM,简称为状态机;状态机的三个组成部分:状态(State)、事件(Event)、动作(Action);其中事件也称为转移条件(Transition Condition),事件触发状态的转移及动作的执行。不过,动作不是必须的,也可能只转移状态,不执行任何动作。
- 状态机模式的三种实现方法:分支逻辑法;查表法;状态模式
2、状态模式的作用
- 状态模式通过将事件触发的状态转移和动作执行,拆分到不同的状态类中,来避免分支判断逻辑
3、状态模式的案例
- 故事背景:在游戏中,马里奥可以变身为多种形态,比如小马里奥(Small Mario)、超级马里奥(Super Mario)、火焰马里奥(Fire Mario)、斗篷马里奥(Cape Mario)等等。在不同的游戏情节下,各个形态会互相转化,并相应的增减积分。比如,初始形态是小马里奥,吃了蘑菇之后就会变成超级马里奥,并且增加 100 积分。
- 故事分析:马里奥形态的转变就是一个状态机。其中,马里奥的不同形态就是状态机中的“状态”,游戏情节(比如吃了蘑菇)就是状态机中的“事件”,加减积分就是状态机中的“动作”。比如,吃蘑菇这个事件,会触发状态的转移:从小马里奥转移到超级马里奥,以及触发动作的执行(增加 100 积分)。
- 故事抽象:
第一种方法:逻辑分支法
State状态类的定义
public enum State { SMALL(0), SUPER(1), FIRE(2), CAPE(3); private int value; private State(int value) { this.value = value; } public int getValue() { return this.value; } }
MarioStateMachine状态机的实现类
public class MarioStateMachine { private int score; private State currentState; public MarioStateMachine() { this.score = 0; this.currentState = State.SMALL; } /** * 吃到蘑菇 */ public void obtainMushRoom() { if (currentState.equals(State.SMALL)) { this.currentState = State.SUPER; this.score += 100; } } /** * 获得斗篷 */ public void obtainCape() { if (currentState.equals(State.SMALL) || currentState.equals(State.SUPER)) { this.currentState = State.CAPE; this.score += 200; } } /** * 获得火焰 */ public void obtainFireFlower() { if (currentState.equals(State.SMALL) || currentState.equals(State.SUPER)) { this.currentState = State.FIRE; this.score += 300; } } /** * 遇到怪物 */ public void meetMonster() { if (currentState.equals(State.SUPER)) { this.currentState = State.SMALL; this.score -= 100; return; } if (currentState.equals(State.CAPE)) { this.currentState = State.SMALL; this.score -= 200; return; } if (currentState.equals(State.FIRE)) { thisthis.currentState = State.SMALL; this.score -= 300; return; } } public int getScore() { return this.score; } public State getCurrentState() { return this.currentState; } }
第二种方法:查表法
用状态转移图来表示之外,状态机还可以用二维表来表示,如下所示。在这个二维表中,第一维表示当前状态,第二维表示事件,值表示当前状态经过事件之后,转移到的新状态及其执行的动作。
Event定义事件类(状态转移条件类)
public enum Event { GOT_MUSHROOM(0), GOT_CAPE(1), GOT_FIRE(2), MET_MONSTER(3); private int value; private Event(int value) { this.value = value; } public int getValue() { return this.value; } }
State状态类的定义
MarioStateMachine定义事件类(状态转移条件类)
public class MarioStateMachine { private int score; private State currentState; /** * 状态转换规则(参考二维表) */ private static final State[][] transitionTable = { {SUPER, CAPE, FIRE, SMALL}, {SUPER, CAPE, FIRE, SMALL}, {CAPE, CAPE, CAPE, SMALL}, {FIRE, FIRE, FIRE, SMALL} }; /** * 积分变化规则(参考二维表) */ private static final int[][] actionTable = { {+100, +200, +300, +0}, {+0, +200, +300, -100}, {+0, +0, +0, -200}, {+0, +0, +0, -300} }; public MarioStateMachine() { this.score = 0; this.currentState = State.SMALL; } public void obtainMushRoom() { executeEvent(Event.GOT_MUSHROOM); } public void obtainCape() { executeEvent(Event.GOT_CAPE); } public void obtainFireFlower() { executeEvent(Event.GOT_FIRE); } public void meetMonster() { executeEvent(Event.MET_MONSTER); } private void executeEvent(Event event) { int stateValue = currentState.getValue(); int eventValue = event.getValue(); this.currentState = transitionTable[stateValue][eventValue]; this.score = actionTable[stateValue][eventValue]; } public int getScore() { return this.score; } public State getCurrentState() { return this.currentState; } }
第二种方法:状态模式法
IMario定义状态接口
public interface IMario { //所有状态类的接口 State getName(); //以下是定义的事件 //获得蘑菇 void obtainMushRoom(); //获得斗篷 void obtainCape(); //获得火焰 void obtainFireFlower(); //遇到怪物 void meetMonster(); }
MarioStateMachine定义状态机类
public class MarioStateMachine { private int score; private IMario currentState; public MarioStateMachine() { this.score = 0; this.currentState = SmallMario.getInstance(); } public void obtainMushRoom() { this.currentState.obtainMushRoom(this); } public void obtainCape() { this.currentState.obtainCape(this); } public void obtainFireFlower() { this.currentState.obtainFireFlower(this); } public void meetMonster() { this.currentState.meetMonster(this); } public int getScore() { return this.score; } public State getCurrentState() { return this.currentState.getName(); } public void setScore(int score) { this.score = score; } public void setCurrentState(IMario currentState) { this.currentState = currentState; } }
各个状态在指定事件(转态转移事件发生时)的实现类
public class SmallMario implements IMario { private static final SmallMario instance = new SmallMario(); private SmallMario() { } public static SmallMario getInstance() { return instance; } @Override public State getName() { return State.SMALL; } @Override public void obtainMushRoom(MarioStateMachine stateMachine) { stateMachine.setCurrentState(SuperMario.getInstance()); stateMachine.setScore(stateMachine.getScore() + 100); } @Override public void obtainCape(MarioStateMachine stateMachine) { stateMachine.setCurrentState(CapeMario.getInstance()); stateMachine.setScore(stateMachine.getScore() + 200); } @Override public void obtainFireFlower(MarioStateMachine stateMachine) { stateMachine.setCurrentState(FireMario.getInstance()); stateMachine.setScore(stateMachine.getScore() + 300); } @Override public void meetMonster(MarioStateMachine stateMachine) { // do nothing... } } // 省略SuperMario、CapeMario、FireMario类...
三、迭代器模式
1、迭代器模式概念
- 概念:迭代器模式(Iterator Design Pattern),也叫作游标模式(Cursor Design Pattern)。它用来遍历集合对象。这里说的“集合对象”也可以叫“容器”“聚合对象”,实际上就是包含一组对象的对象,比如数组、链表、树、图、跳表。
- 价值:迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一。
- 迭代器是用来遍历容器的,所以,一个完整的迭代器模式一般会涉及容器和容器迭代器两部分内容
2、为什么使用迭代器模式
- 对于类似数组和链表这样的数据结构,遍历方式比较简单,直接使用 for 循环来遍历就足够了。但是,对于复杂的数据结构(比如树、图)来说,有各种复杂的遍历方式。比如,树有前中后序、按层遍历,图有深度优先、广度优先遍历等等。如果由客户端代码来实现这些遍历算法,势必增加开发成本,而且容易写错。如果将这部分遍历的逻辑写到容器类中,也会导致容器类代码的复杂性。
- 前面也多次提到,应对复杂性的方法就是拆分。我们可以将遍历操作拆分到迭代器类中。比如,针对图的遍历,我们就可以定义 DFSIterator、BFSIterator 两个迭代器类,让它们分别来实现深度优先遍历和广度优先遍历。
遍历集合一般有三种方式:for 循环、foreach 循环、迭代器遍历。后两种本质上属于一种,都可以看作迭代器遍历。
相对于 for 循环遍历,利用迭代器来遍历有下面三个优势:
- 迭代器模式封装集合内部的复杂数据结构,开发者不需要了解如何遍历,直接使用容器提供的迭代器即可;
- 迭代器模式将集合对象的遍历操作从集合类中拆分出来,放到迭代器类中,让两者的职责更加单一;
- 迭代器模式让添加新的遍历算法更加容易,更符合开闭原则。除此之外,因为迭代器都实现自相同的接口,在开发中,基于接口而非实现编程,替换迭代器也变得更加容易。
3、迭代器模式的类图
注意点:迭代器模式,在遍历过程中数据不能进行删除。如果要实现,需要特殊处理。
一遍迭代,一遍删除数据,会导致不可预期结果(遍历数据不全(漏遍历)和迭代器报错)
迭代器中需要定义 hasNext()、currentItem()、next() 三个最基本的方法。待遍历的容器对象通过依赖注入传递到迭代器类中。容器通过 iterator() 方法来创建迭代器。
3、迭代器模式的案例
List,ArrayList容器类,都实现了迭代器。