本文是学习设计模式后,自己做的笔记。
学习资源有菜鸟教程以及尚硅谷韩顺平图解设计模式。
本文是设计模式——11个行为型设计模式的下半篇。
文章目录
7、备忘录模式
概述:
一种可以存一个对象的某个状态,以便在适当的时候恢复对象的设计模式。
思想就是程序员常用的快捷键:Ctrl + Z
特点:
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
通过一个备忘录类专门存储对象状态。
客户不与备忘录类耦合,与备忘录管理类耦合。
角色:
1、原始对象类
2、状态保存类(内置要保存的属性)
3、状态保存类管理者(聚合状态保存类)
4、客户端
代码实现:
- 创建原始对象类
//1、创建原始对象类
public class Originator {
private String state; //状态信息
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
//获得一个状态保存类
public Memento savaStateMemento(){
return new Memento(state);
}
//从状态保存类中获取状态信息
public void getStateFromMemento(Memento memento) {
state = memento.getState();
}
}
- 创建对象保存类
//2、创建对象保存类
public class Memento {
private String state;
public Memento(String state) {
super();
this.state = state;
}
public String getState() {
return state;
}
}
- 创建保存管理类
//3、创建保存管理类
public class Caretaker {
private List<Memento> mementolist = new ArrayList<Memento>();
public void add(Memento memento) {
mementolist.add(memento);
}
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!");
caretaker.add(originator.savaStateMemento());
originator.setState("状态2!");
caretaker.add(originator.savaStateMemento());
originator.setState("状态3!");
caretaker.add(originator.savaStateMemento());
System.out.println("现在的状态是:"+originator.getState());
System.out.println("回复状态!");
originator.setState(caretaker.get(0).getState());
System.out.println("现在的状态是:"+originator.getState());
}
}
备忘录模式的注意事项和细节
- 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态
- 实现了信息的封装,使得用户不需要关心状态的保存细节
- 如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存, 这个需要注意
- 适用的应用场景:
1.后悔药
2.打游戏时的存档
3.Windows 里的 ctri + z
4.IE 中的后退
5.数据库的事务管理
- 为了节约内存,备忘录模式可以和原型模式配合使用
8、解释器模式
概述:
一种提供了评估语言的语法或表达式的方式的设计模式。
特点:
这种模式实现了一个表达式接口,该接口解释一个特定的上下文。
给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。
可扩展性比较好,灵活。
增加了新的解释表达式的方式。
易于实现简单文法。
角色:
1、上下文类
2、抽象表达式解析类
3、表达式实现类
类图示例:
本例想实现简单的解释器,根据自己的定义来判断输入。
代码实现:
- 创建表达式接口
//1、创建表达式接口
public interface Expression {
public boolean interpret(String context);
}
- 实现接口
//2、实现接口
public class TerminalExpression implements Expression {
private String data;
public TerminalExpression(String data){
this.data = data;
}
@Override
public boolean interpret(String context) {
if(context.contains(data)){
return true;
}
return false;
}
}
public class OrExpression implements Expression {
private Expression expr1 = null;
private Expression expr2 = null;
public OrExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context) {
return expr1.interpret(context) || expr2.interpret(context);
}
}
public class AndExpression implements Expression {
private Expression expr1 = null;
private Expression expr2 = null;
public AndExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context) {
return expr1.interpret(context) && expr2.interpret(context);
}
}
- 调用
//、调用
public class InterpreterPatternDemo {
//规则:Robert 和 John 是男性
public static Expression getMaleExpression(){
Expression robert = new TerminalExpression("Robert");
Expression john = new TerminalExpression("John");
return new OrExpression(robert, john);
}
//规则:Julie 是一个已婚的女性
public static Expression getMarriedWomanExpression(){
Expression julie = new TerminalExpression("Julie");
Expression married = new TerminalExpression("Married");
return new AndExpression(julie, married);
}
public static void main(String[] args) {
Expression isMale = getMaleExpression();
Expression isMarriedWoman = getMarriedWomanExpression();
System.out.println("John is male? " + isMale.interpret("John"));
System.out.println("Julie is a married women? "
+ isMarriedWoman.interpret("Married Julie"));
}
}
解释器模式的注意事项和细节
- 当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程序具有良好的扩展性
- 应用场景:编译器、运算表达式计算、正则表达式、机器人等
- 使用解释器可能带来的问题:解释器模式会引起类膨胀、解释器模式采用递归调用方法,将会导致调试非常复杂、效率可能降低.
9、状态模式
概述:
一种类的行为是基于它的状态改变的设计模式。
主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换。
当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类。
特点:
允许对象在内部状态发生改变时改变它的行为。
对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。
可以解决代码中包含大量与对象状态有关的条件语句。
当一个对象的内在状态改变时,允许改变其行为。看起来像是改变了其类。
使用时要先分析出所有状态。
角色:
1、环境角色类:用于维护State实例,这个实例定义当前状态
2、抽象状态角色类:定义一个接口封装与环境
3、实体状态角色类:为每一状态添加具体行为
类图示例:
本类图想实现的是一个抽奖系统,内部含有可抽奖、不能抽奖、有奖品、没奖品等状态。
步骤代码实现:
- 创建状态抽象类
//1、创建状态抽象类
public abstract class State {
// 扣除积分 - 50
public abstract void deductMoney();
// 是否抽中奖品
public abstract boolean raffle();
// 发放奖品
public abstract void dispensePrize();
}
- 创建状态实体类
//2、创建状态实体类
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("不能发放奖品");
}
}
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());
//System.out.println("抽奖活动结束");
//System.exit(0);
}
}
}
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("奖品发送完了,请下次再参加");
}
}
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("没中奖,不能发放奖品");
}
}
- 创建环境类,用于储存各个状态以及状态行为,这里需要持有所有的状态类,这样子才可以实现针对不同的状态表现出不同的行为
//3、创建主题活动
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) {
// TODO Auto-generated method stub
// 创建活动对象,奖品有1个奖品
RaffleActivity activity = new RaffleActivity(1);
// 我们连续抽300次奖
for (int i = 0; i < 30; i++) {
System.out.println("--------第" + (i + 1) + "次抽奖----------");
// 参加抽奖,第一步点击扣除积分
activity.debuctMoney();
// 第二步抽奖
activity.raffle();
}
}
}
状态模式的注意事项和细节
- 代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中
- 方便维护。将容易产生问题的 if-else 语句删除了,如果把每个状态的行为都放到一个类中,每次调用方法时都要判断当前是什么状态,不但会产出很多 if-else 语句,而且容易出错
- 符合“开闭原则”。容易增删状态
- 会产生很多类。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度
- 应用场景:当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状态要求有不同的行为的时候,可以考虑使用状态模式
10、策略者模式
概述:
一种个类的行为或其算法可以在运行时更改的设计模式。
跟状态模式有明显区别的是,他是基于自己对类进行改变的,而不是状态模式中的基于状态来改变。
策略模式(Strategy Pattern)中,定义算法族(策略组),分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户
这算法体现了几个设计原则,第一、把变化的代码从不变的代码中分离出来;第二、针对接口编程而不是具体类(定义了策略接口);第三、多用组合/聚合,少用继承(客户通过组合方式使用策略)。
跟组合模式的显著区别是他是对类的算法进行组合改变,组合模式是对类的结构进行组合改变。
特点:
创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
让算法变化独立与使用算法的用户。
把变化的代码从不变的代码中分离开来,针对接口编程而不是具体类,多用组合聚合,少用继承。
角色:
1、策略接口
2、具体策略实现类
3、客户端:聚合策略接口
类图示例:
代码实现:
- 创建策略接口
//1、策略接口
public interface FlyBehavior {
void fly();
}
- 创建策略实现类
//2、策略实现类
public class NoFlyBehavior implements FlyBehavior{
public NoFlyBehavior() {
// TODO Auto-generated constructor stub
}
@Override
public void fly() {
System.out.println("没有飞行能力!");
}
}
public class GoodFlyBehavior implements FlyBehavior{
public GoodFlyBehavior() {
}
@Override
public void fly() {
System.out.println("飞行能力好!");
}
}
- 创建对象类,在这里给其定义对应的策略
//3、对象类
public abstract class Duck {
private FlyBehavior flybehavior;
public Duck(FlyBehavior flybehavior) {
super();
this.flybehavior = flybehavior;
}
public abstract void disply();
public void fly() {
if (flybehavior!=null)
flybehavior.fly();
}
}
客户端调用
//4、客户端调用
public class client {
public static void main(String[] args) {
Duck duck = new WildDuck(new GoodFlyBehavior());
duck.fly();
}
}
策略模式的注意事项和细节优点:
优点:
1、算法可以自由切换。
2、避免使用多重条件判断。
3、扩展性良好。
缺点:
1、策略类会增多。
2、所有策略类都需要对外暴露。
- 策略模式的关键是:分析项目中变化部分与不变部分
- 策略模式的核心思想是:多用组合/聚合 少用继承;用行为类组合,而不是行为的继承。更有弹性
- 体现了**“对修改关闭,对扩展开放”**原则,客户端增加行为不用修改原有代码,只要添加一种策略(或者行为)即可,避免了使用多重转移语句(if…else if…else)
- 提供了可以替换继承关系的办法: 策略模式将算法封装在独立的 Strategy 类中使得你可以独立于其 Context 改变它,使它易于切换、易于理解、易于扩展
- 需要注意的是:每添加一个策略就要增加一个类,当策略过多是会导致类数目庞
11、职责链模式
概述:
一种为请求创建了一个接收者对象的链的设计模式。
- 职责链模式(Chain of Responsibility Pattern), 又叫 责任链模式,为请求创建了一个接收者对象的链。这种模式对请求的发送者和接收者进行解耦。
- 职责链模式通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
- 这种类型的设计模式属于行为型模式
特点:
给予请求的类型,对请求的发送者和接收者进行解耦。
每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
角色:
1、抽象请求接收/处理者类(内部聚合自己)
2、实体请求接收/处理者类
3、请求发送类
类图示例:
代码实现:
实现一个学校 OA 系统的采购审批项目,要求审批逐层进行。
- 创建请求类
//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) {
super();
this.type = type;
this.price = price;
this.id = id;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = 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、创建实体请求类
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("请求编号:"+purchaserequest.getId()+"被"+this.name+"处理!");
}else {
approver.processRequest(purchaserequest);
}
}
}
public class DepartmentApprover extends Approver {
public DepartmentApprover(String name) {
super(name);
}
@Override
public void processRequest(PurchaseRequest purchaserequest) {
if(purchaserequest.getPrice()<=5000) {
System.out.println("请求编号:"+purchaserequest.getId()+"被"+this.name+"处理!");
}else {
approver.processRequest(purchaserequest);
}
}
}
public class SchoolMasterApprover extends Approver{
public SchoolMasterApprover(String name) {
super(name);
}
@Override
public void processRequest(PurchaseRequest purchaserequest) {
if(purchaserequest.getPrice()>=10000) {
System.out.println("请求编号:"+purchaserequest.getId()+"被"+this.name+"处理!");
}else {
approver.processRequest(purchaserequest);
}
}
}
- 客户端调用
//4、客户端调用
public class Client {
public static void main(String[] args) {
//创建请求类
PurchaseRequest purchaserequest = new PurchaseRequest(1, 9999, 1);
//创建审批人
DepartmentApprover departmentapprover = new DepartmentApprover("张主任");
CollegeApprover collegeapprover = new CollegeApprover("胡院长");
SchoolMasterApprover master = new SchoolMasterApprover("陈校长");
//设置链状处理对象
departmentapprover.setApprover(collegeapprover);
collegeapprover.setApprover(master);
//开始处理
departmentapprover.processRequest(purchaserequest);
}
}
优点:
1、降低耦合度。它将请求的发送者和接收者解耦。
2、简化了对象。使得对象不需要知道链的结构。
3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
4、增加新的请求处理类很方便。
缺点:
1、不能保证请求一定被接收。
2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
3、可能不容易观察运行时的特征,有碍于除错。
职责链模式注意事项和细节
- 将请求和处理分开,实现解耦,提高系统的灵活性
- 简化了对象,使对象不需要知道链的结构
- 性能会受到影响,特别是在链比较长的时候,因此需控制链中最大节点数量,一般通过在 Handler 中设置一个最大节点数量,在 setNext()方法中判断是否已经超过阀值,超过则不允许该链建立,避免出现超长链无意识地破坏系统性能
- 调试不方便。采用了类似递归的方式,调试时逻辑可能比较复杂
- 最佳应用场景:有多个对象可以处理同一个请求时,比如:多级请求、请假/加薪等审批流程、Java Web 中 Tomcat对 Encoding 的处理、拦截器