解释器模式、状态模式
解释器模式、状态模式
解释器模式
根据不同的业务场景,来对应抽象不同的元素抽象类,抽象类可能有多个子类,然后实现解释一个表达式的处理
一、四则运算问题
通过解释器模式来实现四则运算,如计算 a+b-c 的值,具体要求
1)先输入表达式的形式,比如 a+b+c-d+e, 要求表达式的字母不能重复
2)在分别输入 a ,b, c, d, e 的值
3)最后求出结果:如图
二、传统方案解决四则运算问题分析
1)编写一个方法,接收表达式的形式,然后根据用户输入的数值进行解析,得到结果
2)问题分析:如果加入新的运算符
,比如 * / ( 等等,不利于扩展
,另外让一个方法来解析会造成程序结构混乱, 不够清晰.
3)解决方案:可以考虑使用解释器模式
,即:表达式 -;解释器(可以有多种)-;结果
三、基本介绍
基本介绍
1)在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。这里的词法分析器和语法分析器都可以看做是解释器
2)解释器模式(Interpreter Pattern):
是指给定一个语言(表达式),定义它的文法的一种表示,并定义一个解释器, 使用该解释器来解释语言中的句子(表达式)
3)应用场景
应用可以将一个需要解释执行的语言中的句子表示为一个抽象语法树 一些重复出现的问题可以用一种简单的语言来表达 一个简单语法需要解释的场景
4)这样的例子还有,比如编译器、运算表达式计算、正则表达式、机器人等
四、原理类图
**对原理类图的说明-即(解释器模式的角色及职责)** 1)Context: 是环境角色,`含有解释器之外的全局信息`.
2)AbstractExpression: 表达式的抽象类, 声明一个抽象的解释操作,这个方法为抽象语法树中所有的节点(子类)所共享
3)TerminalExpression: 终结符表达式, 实现与文法中的终结符相关的解释操作
4)NonTermialExpression: 非终结符表达式,为文法中的非终结符实现解释操作
.
5)说明:输入Context 和TerminalExpression 信息通过 Client 输入即可
五、实现四则
1)应用实例要求:
通过解释器模式来实现四则运算, 如计算 a+b-c 的值
2)思路分析和图解(类图):
3)代码实现:
AddExpression加法解释器
/**
* 加法解释器
*/
public class AddExpression extends SymbolExpression {<!-- -->
public AddExpression(Expression left, Expression right) {<!-- -->
super(left, right);
}
//处理相加
//var 仍然是 {a=10,b=20}..
public int interpreter(HashMap;String, Integer; var) {<!-- -->
//super.left.interpreter(var) : 返回 left 表达式对应的值 a = 10
//super.right.interpreter(var): 返回 right 表达式对应值 b = 20
return super.left.interpreter(var) + super.right.interpreter(var);
}
}
Calculator
public class Calculator {<!-- -->
// 定义表达式
private Expression expression;
// 构造函数传参,并解析
public Calculator(String expStr) {<!-- --> // expStr = a+b
// 安排运算先后顺序,先进后出
Stack;Expression; stack = new Stack;;();
// 表达式拆分成字符数组
char[] charArray = expStr.toCharArray();// [a, +, b]
Expression left = null;
Expression right = null;
//遍历我们的字符数组, 即遍历 [a, +, b]
//针对不同的情况,做处理
for (int i = 0; i ; charArray.length; i++) {<!-- -->
switch (charArray[i]) {<!-- -->
case '+':
//
left = stack.pop();// 从 stack 取 出 left =; "a"
right = new VarExpression(String.valueOf(charArray[++i]));// 取出右表达式 "b"
stack.push(new AddExpression(left, right));// 然后根据得到 left 和 right 构建 AddExpresson 加入stack
break;
case '-':
//
left = stack.pop();
right = new VarExpression(String.valueOf(charArray[++i]));
stack.push(new SubExpression(left, right));
break;
default:
//如果是一个 Var 就创建要给 VarExpression 对象,并 push 到 stack
stack.push(new VarExpression(String.valueOf(charArray[i])));
break;
}
}
//当遍历完整个 charArray 数组后,stack 就得到最后
Expression this.expression = stack.pop();
}
public int run(HashMap;String, Integer; var) {<!-- -->
//最后将表达式 a+b 和 var = {a=10,b=20}
//然后传递给 expression 的 interpreter 进行解释执行
return this.expression.interpreter(var);
}
}
主函数
public class ClientTest {<!-- -->
public static void main(String[] args) throws IOException {<!-- -->
String expStr = getExpStr(); // a+b
HashMap;String, Integer; var = getValue(expStr);// var {a=10, b=20}
Calculator calculator = new Calculator(expStr);
System.out.println("运算结果:" + expStr + "=" + calculator.run(var));
}
// 获得表达式
public static String getExpStr() throws IOException {<!-- -->
System.out.print("请输入表达式:");
return (new BufferedReader(new InputStreamReader(System.in))).readLine();
}
// 获得值映射
public static HashMap;String, Integer; getValue(String expStr) throws IOException {<!-- -->
HashMap;String, Integer; map = new HashMap;;();
for (char ch : expStr.toCharArray()) {<!-- -->
if (ch != '+' && ch != '-') {<!-- -->
if (!map.containsKey(String.valueOf(ch))) {<!-- -->
System.out.print("请输入" + String.valueOf(ch) + "的值:");
String in = (new BufferedReader(new InputStreamReader(System.in))).readLine();
map.put(String.valueOf(ch),Integer.valueOf(in));
}
}
}
return map;
}
}
Expression抽象类
/**
* 表达式的抽象类,
* 通过 HashMap 键值对, 可以获取到变量的值
*/
public abstract class Expression {<!-- -->
// a + b - c
// 解释公式和数值, key 就是公式(表达式) 参数[a,b,c], value 就是就是具体值
// HashMap {a=10, b=20}
public abstract int interpreter(HashMap;String, Integer; var);
}
SubExpression
public class SubExpression extends SymbolExpression {<!-- -->
public SubExpression(Expression left, Expression right) {<!-- -->
super(left, right);
}
//求出 left 和 right 表达式相减后的结果
public int interpreter(HashMap;String, Integer; var) {<!-- -->
return super.left.interpreter(var) - super.right.interpreter(var);
}
}
SymbolExpression符号解析器
/**
* 抽象运算符号解析器 这里,每个运算符号,都只和自己左右两个数字有关系,
* 但左右两个数字有可能也是一个解析的结果,无论何种类型,都是 Expression 类的实现类
*/
public class SymbolExpression extends Expression {<!-- -->
protected Expression left;
protected Expression right;
public SymbolExpression(Expression left, Expression right) {<!-- -->
this.left = left;
this.right = right;
}
//因为 SymbolExpression 是让其子类来实现,因此 interpreter 是一个默认实现
@Override
public int interpreter(HashMap;String, Integer; var) {<!-- -->
return 0;
}
}
VarExpression变量的解释器
/**
* 变量的解释器
*/
public class VarExpression extends Expression {<!-- -->
private String key; // key=a,key=b,key=c
public VarExpression(String key) {<!-- -->
this.key = key;
}
// var 就是{a=10, b=20}
// interpreter 根据 变量名称,返回对应的值
@Override
public int interpreter(HashMap;String, Integer; var) {<!-- -->
return var.get(this.key);
}
}
六、注意事项和细节
1)当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程序具有良好的扩展性
2)应用场景:编译器、运算表达式计算、正则表达式、机器人等
3)使用解释器可能带来的问题:解释器模式会引起类膨胀、解释器模式采用递归调用方法,将会导致调试非常复杂、效率可能降低.
状态模式
要使用状态模式,先分析对应使用情况的各个状态类图
对象状态跟行为一 一对应,一个行为对应一个状态;
通过上下文对象记录当前状态,抽象状态接口,然后用每个实现类来对应它的状态和对应操作
一、抽奖活动问题
请编写程序完成 APP 抽奖活动 具体要求如下 :
对象转换 50 积分,中奖概率是 10%
2)奖品数量固定,抽完就不能抽奖
3)活动有四个状态: 可以抽奖、不能抽奖、发放奖品和奖品领完
4)活动的四个状态转换关系图(下图)
二、状态模式基本介绍
基本介绍
1)状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一 一对应
的,状态之间可以相互转换
2)当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类
3)状态发生了变化,那行为也随之改变
三、 状态模式的原理类图
**对原理类图的说明-即(状态模式的角色及职责)** > 1)Context 类:为环境角色, 用于维护 State 实例,这个实例`定义当前状态` 2)State :是一个接口,抽象状态角色,封装与 Context 的一个特点接口相关行为 3)ConcreteState: `具体的状态`,每个子类实现一个与 Context 的一个状态相关行为
四、状态模式解决 APP 抽奖问
1)应用实例要求
完成 APP 抽奖活动项目,使用状态模式.
2)思路分析和图解(类图)
-定义出一个接口叫状态接口,每个状态都实现它。
-接口有扣除积分方法、抽奖方法、发放奖品方法
3)代码实现
CanRaffleState可抽奖的状态
/**
* 可抽奖的状态
*/
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("没中奖,不能发放奖品");
}
}
主函数
public class ClientTest {<!-- --> public static void main(String[] args) {<!-- --> // 创建活动对象,奖品有 1 个奖品 RaffleActivity activity = new RaffleActivity(1); // 我们连续抽 300 次奖 for (int i = 0; i ; 300; i++) {<!-- --> System.out.println("--------第" + (i + 1) + "次抽奖----------"); // 参加抽奖,第一步点击扣除积分 activity.debuctMoney(); // 第二步抽奖 activity.raffle(); } }}
DispenseOutState
/*** 奖品发放完毕状态* 说明,当我们 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("奖品发送完了,请下次再参加"); } }
DispenseState
/*** 发放奖品的状态*/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); } } }
NoRaffleState不能抽奖状态
/*** 不能抽奖状态*/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("不能发放奖品"); } }
RaffleActivity抽奖活动,也就是content上下文
/*** 抽奖活动,也就是content上下文*/@Datapublic 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(); } } //每领取一次奖品,count-- public int getCount() {<!-- --> int curCount = count; count--; return curCount; } public void setCount(int count) {<!-- --> this.count = count; } }
State状态抽象类
/*** 状态抽象类*/public abstract class State {<!-- --> // 扣除积分 - 50 public abstract void deductMoney(); // 是否抽中奖品 public abstract boolean raffle(); // 发放奖品 public abstract void dispensePrize();}
五、状态模式的注意事项和细节
1)代码有很强的
可读性
。状态模式将每个状态的行为封装到对应的一个类中
2)方便维护
。将容易产生问题的 if-else 语句删除了,如果把每个状态的行为都放到一个类中,每次调用方法时都要判断当前是什么状态,不但会产出很多 if-else 语句,而且容易出错
3)符合“开闭原则”。容易增删状态
4)会产生很多类
。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度
5)应用场景:
当一个事件或者对象有很多种状态,状态之间会相互转换,对不同的状态要求有不同的行为的时候, 可以考虑使用状态模式