1、备忘录模式(Memento Pattern)
1、介绍
1、概述
(1)备忘录模式(Memento Pattern)在不破坏封装性的前提下,捕获一个对象的内 部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保 存的状态。
(2)可以这里理解备忘录模式:现实生活中的备忘录是用来记录某些要去做的事情, 或者是记录已经达成的共同意见的事情,以防忘记了。而在软件层面,备忘录 模式有着相同的含义,备忘录对象主要用来记录一个对象的某种状态,或者某 些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作。
传统的方法就是是简单地做备份,new出另外一个对象出来,再把需要备份的数据放到 这个新对象,但这就暴露了对象内部的细节。
2、原理类图
(1)originator : 对象(需要保存状态的对象) 。
(2)Memento : 备忘录对象,负责保存好记录,即Originator内部 状态。
(3)Caretaker: 守护者对象,负责保存多个备忘录对象, 使用集合管理,提高效率。
说明:如果希望保存多个originator对象的不同时间的状态,也可以,只需要HashMap 。
2、案例
1、需求
游戏角色有攻击力和防御力,在大战Boss前保存自身的状态(攻击力和防御力),当大 战Boss后攻击力和防御力下降,从备忘录对象恢复到大战前的状态。
2、代码
(1)创建原生对象
package com.atguigu.memento.theory;
public class Originator {
private String state;//状态信息
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
//编写一个方法,可以保存一个状态对象 Memento
//因此编写一个方法,返回 Memento
public Memento saveStateMemento() {
return new Memento(state);
}
//通过备忘录对象,恢复状态
public void getStateFromMemento(Memento memento) {
state = memento.getState();
}
}
(2)创建备忘录对象
public class Memento {
private String state;
//构造器 将state放到属性中去
public Memento(String state) {
super();
this.state = state;
}
public String getState() {
return state;
}
}
(3)创建备忘录对象执行者
import java.util.ArrayList;
import java.util.List;
//备忘录对象统一管理
public class Caretaker {
//在List 集合中会有很多的备忘录对象
private List<Memento> mementoList = new ArrayList<Memento>();
public void add(Memento memento) {
mementoList.add(memento);
}
//获取到第index个Originator 的 备忘录对象(即保存状态)
public Memento get(int index) {
return mementoList.get(index);
}
}
(4)客户端
public class Client {
public static void main(String[] args) {
Originator originator = new Originator(); //创建一个原生对象
Caretaker caretaker = new Caretaker();
originator.setState(" 状态#1 攻击力 100 "); //设置状态
//保存了当前的状态
caretaker.add(originator.saveStateMemento());
//保存状态2
originator.setState(" 状态#2 攻击力 80 ");
caretaker.add(originator.saveStateMemento());
System.out.println("当前的状态是 =" + originator.getState());
//希望得到状态 1, 将 originator 恢复到状态1
originator.getStateFromMemento(caretaker.get(0));
System.out.println("恢复到状态1 , 当前的状态是");
System.out.println("当前的状态是 =" + originator.getState());
}
}
3、细节
1、实现了信息的封装,使得用户不需要关心状态的保存细节。
2、如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定 的内存, 这个需要注意。
3、为了节约内存,备忘录模式可以和原型模式配合使用。
2、解释器模式(Interpreter Pattern)
1、介绍
1、概述
解释器模式(Interpreter Pattern):是指给定一个语言(表达式),定义它的文法 的一种表示,并定义一个解释器,使用该解释器来解释语言中的句子(表达式)。
2、应用场景
可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
3、原理类图
2、案例
1、需求
通过该模式,来实现四则运算,比如计算a+b的值。
3、状态模式(State Pattern)
1、介绍
1、概述
状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外 输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换。
当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了 其类。
2、APP抽奖问题
(1)假如每参加一次这个活动要 扣除用户50积分,中奖概率 是10%。
(2)奖品数量固定,抽完就不能抽奖
(3)活动有四个状态: 可以抽奖、 不能抽奖、发放奖品和奖品 领完
3、原理类图
2、案例
1、代码
(1)状态抽象类
public abstract class State {
// 扣除积分 - 50
public abstract void deductMoney();
// 是否抽中奖品
public abstract boolean raffle();
// 发放奖品
public abstract void dispensePrize();
}
(2)四种状态
package com.atguigu.state;
/**
* 不能抽奖状态
*/
public class NoRaffleState extends State {
// 初始化时传入活动引用,扣除积分后改变其状态
RaffleActivity activity;
public NoRaffleState(RaffleActivity activity) {
this.activity = activity;
}
// 当前状态可以扣积分 , 扣除后,将状态设置成可以抽奖状态
@Override
public void deductMoney() {
System.out.println("扣除50积分成功,您可以抽奖了");
activity.setState(activity.getCanRaffleState());
}
// 当前状态不能抽奖
@Override
public boolean raffle() {
System.out.println("扣了积分才能抽奖喔!");
return false;
}
// 当前状态不能发奖品
@Override
public void dispensePrize() {
System.out.println("不能发放奖品");
}
}
package com.atguigu.state;
import java.util.Random;
/**
* 可以抽奖的状态
*/
public class CanRaffleState extends State {
RaffleActivity activity;
public CanRaffleState(RaffleActivity activity) {
this.activity = activity;
}
//已经扣除了积分,不能再扣
@Override
public void deductMoney() {
System.out.println("已经扣取过了积分");
}
//可以抽奖, 抽完奖后,根据实际情况,改成新的状态
@Override
public boolean raffle() {
System.out.println("正在抽奖,请稍等!");
Random r = new Random();
int num = r.nextInt(10);
// 10%中奖机会
if (num > 0 ) {
// 改变活动状态为发放奖品 context
activity.setState(activity.getDispenseState());
return true;
} else {
System.out.println("很遗憾没有抽中奖品!");
// 改变状态为不能抽奖
activity.setState(activity.getNoRafflleState());
return false;
}
}
// 不能发放奖品
@Override
public void dispensePrize() {
System.out.println("没中奖,不能发放奖品");
}
}
package com.atguigu.state;
/**
* 发放奖品的状态
*/
public class DispenseState extends State {
// 初始化时传入活动引用,发放奖品后改变其状态
RaffleActivity activity;
public DispenseState(RaffleActivity activity) {
this.activity = activity;
}
@Override
public void deductMoney() {
System.out.println("不能扣除积分");
}
@Override
public boolean raffle() {
System.out.println("不能抽奖");
return false;
}
//发放奖品
@Override
public void dispensePrize() {
if (activity.getCount() > 0) {
System.out.println("恭喜中奖了");
// 改变状态为不能抽奖
activity.setState(activity.getNoRafflleState());
} else {
System.out.println("很遗憾,奖品发送完了");
// 改变状态为奖品发送完毕, 后面我们就不可以抽奖
activity.setState(activity.getDispensOutState());
}
}
}
package com.atguigu.state;
/**
* 奖品发放完毕状态
* 说明,当我们activity 改变成 DispenseOutState, 抽奖活动结束
*/
public class DispenseOutState extends State {
// 初始化时传入活动引用
RaffleActivity activity;
public DispenseOutState(RaffleActivity activity) {
this.activity = activity;
}
@Override
public void deductMoney() {
System.out.println("奖品发送完了,请下次再参加");
}
@Override
public boolean raffle() {
System.out.println("奖品发送完了,请下次再参加");
return false;
}
@Override
public void dispensePrize() {
System.out.println("奖品发送完了,请下次再参加");
}
}
(3)抽奖活动
package com.atguigu.state;
/**
* 抽奖活动
*/
public class RaffleActivity {
// state 表示活动当前的状态,是变化
State state = null;
// 奖品数量
int count = 0;
// 四个属性,表示四种状态
State noRafflleState = new NoRaffleState(this);
State canRaffleState = new CanRaffleState(this);
State dispenseState = new DispenseState(this);
State dispensOutState = new DispenseOutState(this);
//构造器
//1. 初始化当前的状态为 noRafflleState(即不能抽奖的状态)
//2. 初始化奖品的数量
public RaffleActivity( int count) {
this.state = getNoRafflleState();
this.count = count;
}
//扣分, 调用当前状态的 deductMoney
public void debuctMoney(){
state.deductMoney();
}
//抽奖
public void raffle(){
// 如果当前的状态是抽奖成功
if(state.raffle()){
//领取奖品
state.dispensePrize();
}
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
//这里请大家注意,每领取一次奖品,count--
public int getCount() {
int curCount = count;
count--;
return curCount;
}
public void setCount(int count) {
this.count = count;
}
public State getNoRafflleState() {
return noRafflleState;
}
public void setNoRafflleState(State noRafflleState) {
this.noRafflleState = noRafflleState;
}
public State getCanRaffleState() {
return canRaffleState;
}
public void setCanRaffleState(State canRaffleState) {
this.canRaffleState = canRaffleState;
}
public State getDispenseState() {
return dispenseState;
}
public void setDispenseState(State dispenseState) {
this.dispenseState = dispenseState;
}
public State getDispensOutState() {
return dispensOutState;
}
public void setDispensOutState(State dispensOutState) {
this.dispensOutState = dispensOutState;
}
}
(4)客户端
public class ClientTest {
public static void main(String[] args) {
// 创建活动对象,奖品有1个奖品
RaffleActivity activity = new RaffleActivity(1);
// 我们连续抽300次奖
for (int i = 0; i < 10; i++) {
System.out.println("--------第" + (i + 1) + "次抽奖----------");
// 参加抽奖,第一步点击扣除积分
activity.debuctMoney();
// 第二步抽奖
activity.raffle();
}
}
}
3、细节
1、代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中。
2、方便维护。将容易产生问题的if-else语句删除了,如果把每个状态的行为都放到一 个类中,每次调用方法时都要判断当前是什么状态,不但会产出很多if-else语句, 而且容易出错。
4、策略模式(Strategy Pattern)
1、介绍
1、概述
该模式定义算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
该算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来; 第二、针对接口编程而不是具体类(定义了策略接口);第三、多用组合/聚合, 少用继承(客户通过组合方式使用策略)。
2、原理类图
从上图可以看到,客户context 有成员变量strategy或者其他的策略接口 ,至于需要使用到哪个策略,我们可以在构造器中指定。
2、案例
1、需求
有各种鸭子(比如 野鸭、北京鸭、水鸭等, 鸭子有各种行为,比如 叫、飞行等) ,此外,显示鸭子的信息。
使用传统方式时,存在一下问题:对于其它鸭子,都继承了Duck类,所以fly让所有子类都会飞了,这是不正确的。
2、代码
(1)Duck类
package com.atguigu.strategy.improve;
public abstract class Duck {
//属性, 策略接口
FlyBehavior flyBehavior;
//其它属性<->策略接口
QuackBehavior quackBehavior;
public Duck() {
}
public abstract void display();//显示鸭子信息
public void quack() {
System.out.println("鸭子嘎嘎叫~~");
}
public void swim() {
System.out.println("鸭子会游泳~~");
}
public void fly() {
if (flyBehavior != null) {
//说明该接口有子类赋值
flyBehavior.fly();
}
}
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
}
(2)飞行为接口
public interface FlyBehavior {
void fly(); // 子类具体实现
}
(3)叫行为接口
public interface QuackBehavior {
void quack();//子类实现
}
(4)针对飞行的策略
//针对飞行有不同的策略
public class GoodFlyBehavior implements FlyBehavior {
@Override
public void fly() {
System.out.println(" 飞翔技术高超 ~~~");
}
}
public class NoFlyBehavior implements FlyBehavior {
@Override
public void fly() {
System.out.println(" 不会飞翔 ");
}
}
(5)野鸭
public class WildDuck extends Duck {
//构造器,传入FlyBehavor 的对象
public WildDuck() {
//按照下面的格式书写
flyBehavior = new GoodFlyBehavior();
}
@Override
public void display() {
System.out.println(" 这是野鸭 ");
}
}
(6)客户端测试
public class Client {
public static void main(String[] args) {
WildDuck wildDuck = new WildDuck();
wildDuck.fly();//
//动态改变某个对象的行为, 野鸭 不能飞
wildDuck.setFlyBehavior(new NoFlyBehavior());
System.out.println("野鸭的实际飞翔能力");
wildDuck.fly();
}
}
3、细节
(1)策略模式的关键是:分析项目中变化部分与不变部分。
(2)策略模式的核心思想是:多用组合/聚合 少用继承;用行为类组合,而不是行为的 继承。更有弹性。
(3)体现了“对修改关闭,对扩展开放”原则,客户端增加行为不用修改原有代码,只 要添加一种策略(或者行为)即可,避免了使用多重转移语句(if…else if…else)。
(4)提供了可以替换继承关系的办法: 策略模式将算法封装在独立的Strategy类中使得 你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展。
(5)需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞大
5、职责链模式(Chain of Responsibility Pattern)
1、介绍
1、概述
职责链模式, 又叫责任链模式,为请求创建了一个接收者对象的链(简单示意图)。这种模式对请求的发送者和接收者进行解耦。
职责链模式通常每个接收者都包含对另一个接 收者的引用。如果一个对象不能处理该请求, 那么它会把相同的请求传给下一个接收者,依此类推。
该模式 使多个对象都有机会处理请求,从而避 免请求的发送者和接收者之间的耦合关 系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理 它为止。
2、原理类图
(1)Handler : 抽象的处理者, 定义了一个处理请求的接口, 同时含义另外Handler。
(2)ConcreteHandlerA , B:是具体的处理者, 处理它自己负责的请求, 可以访问它的后继者(即下一个处理者), 如果可以处理当前请求,则处理,否则就将该请求交个后继者去处理,从而形成一个职责链。
(3)Request:包含很多属性,表示一个请求。
2、案例
1、需求
编写程序完成学校OA系统的采购审批项目:
如果金额 小于等于5000, 由教学主任审批;
如果金额 小于等于10000, 由院长审批 ;
如果金额 小于等于30000, 由副校长审批;
如果金额 超过30000以上,有校长审批。
2、传统方式
传统方式是:接收到一个采购请求后,根据采购金额来调用对应的Approver (审批 人)完成审批。
客户端这里会使用到分支判断(比如 switch) 来对不同的采 购请求处理, 这样就存在如下问题:
(1) 如果各个级别的人员审批金额发生变化,在客户端的也需要变化;
(2) 客户端必须明确的知道,有多少个审批级别和访问。
这样 对一个采购请求进行处理 和 Approver (审批人) 就存在强耦合关系,不利于代码的扩展和维护。
3、代码
(1)请求类
public class PurchaseRequest {
private int type = 0; //请求类型
private float price = 0.0f; //请求金额
private int id = 0;
//构造器
public PurchaseRequest(int type, float price, int id) {
this.type = type;
this.price = price;
this.id = id;
}
public int getType() {
return type;
}
public float getPrice() {
return price;
}
public int getId() {
return id;
}
}
(2)抽象处理者
public abstract class Approver {
Approver approver; //下一个处理者
String name; // 名字
public Approver(String name) {
this.name = name;
}
//下一个处理者
public void setApprover(Approver approver) {
this.approver = approver;
}
//处理审批请求的方法,得到一个请求, 处理是子类完成,因此该方法做成抽象
public abstract void processRequest(PurchaseRequest purchaseRequest);
}
(3)具体处理者
//1、系主任
public class DepartmentApprover extends Approver {
public DepartmentApprover(String name) {
super(name);
}
@Override
public void processRequest(PurchaseRequest purchaseRequest) {
if (purchaseRequest.getPrice() <= 5000) {
System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
} else {
//处理不了,让后继者去处理
approver.processRequest(purchaseRequest);
}
}
}
//2、院长级别
public class CollegeApprover extends Approver {
public CollegeApprover(String name) {
super(name);
}
@Override
public void processRequest(PurchaseRequest purchaseRequest) {
if (purchaseRequest.getPrice() < 5000 && purchaseRequest.getPrice() <= 10000) {
System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
} else {
approver.processRequest(purchaseRequest);
}
}
}
//3、副校长级别
public class ViceSchoolMasterApprover extends Approver {
public ViceSchoolMasterApprover(String name) {
super(name);
}
@Override
public void processRequest(PurchaseRequest purchaseRequest) {
if (purchaseRequest.getPrice() < 10000 && purchaseRequest.getPrice() <= 30000) {
System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
} else {
approver.processRequest(purchaseRequest);
}
}
}
//4、校长级别
public class SchoolMasterApprover extends Approver {
public SchoolMasterApprover(String name) {
super(name);
}
@Override
public void processRequest(PurchaseRequest purchaseRequest) {
if (purchaseRequest.getPrice() > 30000) {
System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
} else {
approver.processRequest(purchaseRequest);
}
}
}
(4)客户端
public class Client {
public static void main(String[] args) {
//创建一个请求
PurchaseRequest purchaseRequest = new PurchaseRequest(1, 31000, 1);
//创建相关的审批人
DepartmentApprover departmentApprover = new DepartmentApprover("张主任");
CollegeApprover collegeApprover = new CollegeApprover("李院长");
ViceSchoolMasterApprover viceSchoolMasterApprover = new ViceSchoolMasterApprover("王副校");
SchoolMasterApprover schoolMasterApprover = new SchoolMasterApprover("佟校长");
//需要将各个审批级别的下一个设置好 (处理人构成环形: )
departmentApprover.setApprover(collegeApprover);
collegeApprover.setApprover(viceSchoolMasterApprover);
viceSchoolMasterApprover.setApprover(schoolMasterApprover);
schoolMasterApprover.setApprover(departmentApprover);
departmentApprover.processRequest(purchaseRequest);
viceSchoolMasterApprover.processRequest(purchaseRequest);
}
}
3、细节
1、简化了对象,使对象不需要知道链的结构。
2、性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般 通过在Handler中设置一个最大节点数量,在setNext()方法中判断是否已经超过阀值, 超过则不允许该链建立,避免出现超长链无意识地破坏系统性能。
3、调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂。
eApprover collegeApprover = new CollegeApprover(“李院长”);
ViceSchoolMasterApprover viceSchoolMasterApprover = new ViceSchoolMasterApprover(“王副校”);
SchoolMasterApprover schoolMasterApprover = new SchoolMasterApprover(“佟校长”);
//需要将各个审批级别的下一个设置好 (处理人构成环形: )
departmentApprover.setApprover(collegeApprover);
collegeApprover.setApprover(viceSchoolMasterApprover);
viceSchoolMasterApprover.setApprover(schoolMasterApprover);
schoolMasterApprover.setApprover(departmentApprover);
departmentApprover.processRequest(purchaseRequest);
viceSchoolMasterApprover.processRequest(purchaseRequest);
}
}
### 3、细节
1、简化了对象,使对象不需要知道链的结构。
2、性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般 通过在Handler中设置一个最大节点数量,在setNext()方法中判断是否已经超过阀值, 超过则不允许该链建立,避免出现超长链无意识地破坏系统性能。
3、调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂。
4、最佳应用场景:有多个对象可以处理同一个请求时,比如:多级请求、请假/加薪 等审批流程、Java Web中Tomcat对Encoding的处理、拦截器。