- 4.行为型模式(11种)
- 4.1.模板方法模式
- 4.2.策略模式
- 4.3.命令模式
- 4.3.1.概述
- 4.3.2.结构
- 4.3.3.案例实现
- 4.3.4.优缺点
- 4.3.5.使用场景
- 4.3.6.JDK源码解析——Runable
- 4.4.责任链模式
- 4.5.状态模式
- 4.5.1.概述
- 4.5.2.结构
- 4.5.3.案例实现
- 4.5.4.优缺点
- 4.5.5.使用场景
- 4.6.观察者模式
- 4.7.中介者模式
- 4.7.1.概述
- 4.7.2.结构
- 4.7.3.案例实现
- 4.7.4.优缺点
- 4.7.5.使用场景
- 4.8.迭代器模式
- 4.9.访问者模式
- 4.9.1.概述
- 4.9.2.结构
- 4.9.2.案例实现
- 4.9.3.优缺点
- 4.9.4.使用场景
- 4.9.5.扩展
- 4.10.备忘录模式
- 4.10.1.概述
- 4.10.2.结构
- 4.10.3.案例实现
- 4.10.4.优缺点
- 4.10.5.使用场景
- 4.11.解释器模式
- 4.11.1概述
- 4.11.2.结构
- 4.11.3.案例实现
- 4.11.4.优缺点
- 4.11.5.使用场景
本文章笔记整理来自黑马视频https://www.bilibili.com/video/BV1Np4y1z7BU,相关资料可在评论区获取。
详解23种设计模式(基于Java)—— 设计模式相关内容介绍(一 / 五) 详解23种设计模式(基于Java)—— 创建者模式(二 / 五) 详解23种设计模式(基于Java)—— 结构型模式(三 / 五) 详解23种设计模式(基于Java)—— 综合练习之自定义Spring IoC(五 / 五)
4.行为型模式(11种)(1)行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
(2)行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。
(3)行为型模式分为以下 11 种:模板方法模式、策略模式、命令模式、职责链模式、状态模式、观察者模式、中介者模式、迭代器模式、访问者模式、备忘录模式。其中除了模板方法模式和解释器模式是类行为型模式,其他的全部属于对象行为型模式。
4.1.模板方法模式有关模板方法模式的具体知识可以参考 Java 设计模式——模板方法模式这篇文章。
4.2.策略模式有关策略模式的具体知识可以参考 Java 设计模式——策略模式这篇文章。
4.3.命令模式 4.3.1.概述(1)日常生活中,我们出去吃饭都会遇到下面的场景: (2)命令模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加与管理。
命令模式包含以下主要角色: (1)抽象命令类(Command)角色: 定义命令的接口,声明执行的方法。 (2)具体命令(Concrete Command)角色: 具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。 (3)实现者/接收者(Receiver)角色: 接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。 (4)调用者/请求者(Invoker)角色: 要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
4.3.3.案例实现将上面的案例用代码实现,那就需要分析命令模式的角色在该案例中由谁来充当。 ① 服务员: 就是调用者角色,由她来发起命令。 ② 资深大厨: 就是接收者角色,真正命令执行的对象。 ③ 订单: 命令中包含订单。 类图如下: 具体实现代码如下: Command.java
package com.itheima.patterns.behaviorpattern.command;
//抽象命令接口
public interface Command {
void execute();
}
Order.java
package com.itheima.patterns.behaviorpattern.command;
import java.util.HashMap;
import java.util.Map;
//订单类
public class Order {
//餐桌号码
private int diningTable;
//所下的餐品及份数
private Map foodDir = new HashMap();
public int getDiningTable() {
return diningTable;
}
public void setDiningTable(int diningTable) {
this.diningTable = diningTable;
}
public Map getFoodDir() {
return foodDir;
}
public void setFood(String name,int num) {
foodDir.put(name,num);
}
}
SeniorChef.java
package com.itheima.patterns.behaviorpattern.command;
//厨师类
public class SeniorChef {
public void makeFood(String name,int num){
System.out.println(num + "份" + name);
}
}
OrderCommand.java
package com.itheima.patterns.behaviorpattern.command;
import java.util.Map;
import java.util.Set;
//具体的命令类
public class OrderCommand implements Command{
//持有接收者对象
private SeniorChef receiver;
private Order order;
public OrderCommand(SeniorChef receiver, Order order) {
this.receiver = receiver;
this.order = order;
}
@Override
public void execute() {
System.out.println(order.getDiningTable() + "桌的订单:");
Map foodDir = order.getFoodDir();
//遍历map集合
Set keys = foodDir.keySet();
for (String foodName : keys) {
receiver.makeFood(foodName,foodDir.get(foodName));
}
System.out.println(order.getDiningTable() + "桌的饭准备完毕!");
}
}
Waitor.java
package com.itheima.patterns.behaviorpattern.command;
import java.util.ArrayList;
import java.util.List;
//服务员类(属于请求者角色)
public class Waitor {
//持有多个命令对象
private List commands = new ArrayList();
public void setCommand(Command cmd){
//将cmd对象存储到List集合中
commands.add(cmd);
}
//发起命令功能,喊订单来了
public void orderUp(){
System.out.println("服务员:大厨,新订单来了。。。。");
//遍历list集合
for (Command command : commands) {
if(command != null) {
command.execute();
}
}
}
}
Client.java
package com.itheima.patterns.behaviorpattern.command;
public class Client {
public static void main(String[] args) {
//创建第一个订单对象
Order order1 = new Order();
order1.setDiningTable(1);
order1.setFood("西红柿鸡蛋面",1);
order1.setFood("小杯可乐",2);
//创建第二个订单对象
Order order2 = new Order();
order2.setDiningTable(2);
order2.setFood("尖椒肉丝盖饭",1);
order2.setFood("小杯雪碧",1);
//创建厨师对象
SeniorChef receiver = new SeniorChef();
//创建命令对象
OrderCommand cmd1 = new OrderCommand(receiver,order1);
OrderCommand cmd2 = new OrderCommand(receiver,order2);
//创建调用者(服务员对象)
Waitor invoke = new Waitor();
invoke.setCommand(cmd1);
invoke.setCommand(cmd2);
//让服务员发起命令
invoke.orderUp();
}
}
4.3.4.优缺点
(1)优点 ① 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。 ② 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。 ③ 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。 ④ 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。 (2)缺点 ① 使用命令模式可能会导致某些系统有过多的具体命令类。 ② 系统结构更加复杂
4.3.5.使用场景(1)系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。 (2)系统需要在不同的时间指定请求、将请求排队和执行请求。 (3)系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
4.3.6.JDK源码解析——RunableRunable是一个典型命令模式,Runnable担当命令的角色,Thread充当的是调用者,start方法就是其执行方法。
//命令接口(抽象命令角色)
public interface Runnable {
public abstract void run();
}
//调用者
public class Thread implements Runnable {
private Runnable target;
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
}
调用者会调用一个native方法start0(),调用系统方法,开启一个线程。而接收者是对程序员开放的,可以自己定义接收者。
/**
* jdk Runnable 命令模式
* TurnOffThread : 属于具体
* */
public class TurnOffThread implements Runnable{
private Receiver receiver;
public TurnOffThread(Receiver receiver) {
this.receiver = receiver;
}
public void run() {
receiver.turnOFF();
}
}
//测试类
public class Demo {
public static void main(String[] args) {
Receiver receiver = new Receiver();
TurnOffThread turnOffThread = new TurnOffThread(receiver);
Thread thread = new Thread(turnOffThread);
thread.start();
}
}
4.4.责任链模式
有关责任链模式的具体知识可以参考Java 设计模式——责任链模式这篇文章。
4.5.状态模式 4.5.1.概述(1)【例】通过按钮来控制一个电梯的状态,电梯有开门状态,关门状态,停止状态,运行状态。每一种状态改变,都有可能要根据其他状态来更新处理。例如,如果电梯门现在处于运行时状态,就不能进行开门操作,而如果电梯门是停止状态,就可以执行开门操作。其类图如下: 具体实现代码如下: ILift.java
package com.itheima.patterns.behaviorpattern.state.before;
public interface ILift {
//定义四个电梯状态的常量
int OPENING_STATE = 1;
int CLOSING_STATE = 2;
int RUNNING_STATE = 3;
int STOPPING_STATE = 4;
//设置电梯状态的功能
void setState(int state);
//电梯操作功能
void open();
void close();
void run();
void stop();
}
Lift.java
package com.itheima.patterns.behaviorpattern.state.before;
//电梯类(ILift的子实现类)
public class Lift implements ILift {
//声明一个记录当前电梯的状态
private int state;
public void setState(int state) {
this.state = state;
}
public void open() {
switch (state) { //当前电梯状态
case OPENING_STATE :
//什么事都不做
break;
case CLOSING_STATE :
System.out.println("电梯打开了...");
//设置当前电梯状态为开启状态
setState(OPENING_STATE);
break;
case STOPPING_STATE :
System.out.println("电梯打开了...");
//设置当前电梯状态为开启状态
setState(OPENING_STATE);
break;
case RUNNING_STATE :
//什么事都不做
break;
}
}
public void close() {
switch (this.state) {
case OPENING_STATE:
System.out.println("电梯关门了。。。");//只有开门状态可以关闭电梯门,可以对应电梯状态表来看
this.setState(CLOSING_STATE);//关门之后电梯就是关闭状态了
break;
case CLOSING_STATE:
//do nothing //已经是关门状态,不能关门
break;
case RUNNING_STATE:
//do nothing //运行时电梯门是关着的,不能关门
break;
case STOPPING_STATE:
//do nothing //停止时电梯也是关着的,不能关门
break;
}
}
public void run() {
switch (this.state) {
case OPENING_STATE://电梯不能开着门就走
//do nothing
break;
case CLOSING_STATE://门关了,可以运行了
System.out.println("电梯开始运行了。。。");
this.setState(RUNNING_STATE);//现在是运行状态
break;
case RUNNING_STATE:
//do nothing 已经是运行状态了
break;
case STOPPING_STATE:
System.out.println("电梯开始运行了。。。");
this.setState(RUNNING_STATE);
break;
}
}
public void stop() {
switch (this.state) {
case OPENING_STATE: //开门的电梯已经是是停止的了(正常情况下)
//do nothing
break;
case CLOSING_STATE://关门时才可以停止
System.out.println("电梯停止了。。。");
this.setState(STOPPING_STATE);
break;
case RUNNING_STATE://运行时当然可以停止了
System.out.println("电梯停止了。。。");
this.setState(STOPPING_STATE);
break;
case STOPPING_STATE:
//do nothing
break;
}
}
}
Client.java
package com.itheima.patterns.behaviorpattern.state.before;
public class Client {
public static void main(String[] args) {
//创建电梯对象
Lift lift = new Lift();
//设置当前电梯的状态
lift.setState(ILift.OPENING_STATE);
//打开
lift.open();
lift.close();
lift.run();
lift.stop();
}
}
问题分析: ① 使用了大量的switch…case这样的判断(if…else也是一样),使程序的可阅读性变差。 ② 扩展性很差,如果新加了断电的状态,则需要修改上面的判断逻辑。 (2)状态模式:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
4.5.2.结构状态模式包含以下主要角色: (1)环境(Context)角色: 也称为上下文,它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。 (2)抽象状态(State)角色: 定义一个接口,用以封装环境对象中的特定状态所对应的行为。 (3)具体状态(Concrete State)角色: 实现抽象状态所对应的行为。
4.5.3.案例实现对上述电梯的案例使用状态模式进行改进。类图如下: 具体实现代码如下: LiftState.java(抽象状态类)
package com.itheima.patterns.behaviorpattern.state.after;
//抽象状态类
public abstract class LiftState {
//声明环境角色类变量
protected Context context;
public void setContext(Context context) {
this.context = context;
}
//电梯开启操作
public abstract void open();
//电梯关闭操作
public abstract void close();
//电梯运行操作
public abstract void run();
//电梯停止操作
public abstract void stop();
}
OpeningState.java(具体状态类)
package com.itheima.patterns.behaviorpattern.state.after;
//电梯开启状态类
public class OpeningState extends LiftState {
//当前状态要执行的方法
public void open() {
System.out.println("电梯开启。。。");
}
public void close() {
//修改状态
super.context.setLiftState(Context.CLOSING_STATE);
//调用当前状态中的context中的close方法
super.context.close();
}
public void run() {
//什么都不做
}
public void stop() {
//什么都不做
}
}
ClosingState.java(具体状态类)
package com.itheima.patterns.behaviorpattern.state.after;
//电梯关闭状态类
public class ClosingState extends LiftState {
@Override
//电梯门关闭,这是关闭状态要实现的动作
public void close() {
System.out.println("电梯门关闭...");
}
//电梯门关了再打开,逗你玩呢,那这个允许呀
@Override
public void open() {
super.context.setLiftState(Context.OPENING_STATE);
super.context.open();
}
//电梯门关了就跑,这是再正常不过了
@Override
public void run() {
super.context.setLiftState(Context.RUNNING_STATE);
super.context.run();
}
//电梯门关着,我就不按楼层
@Override
public void stop() {
super.context.setLiftState(Context.STOPPING_STATE);
super.context.stop();
}
}
RunningState.java(具体状态类)
package com.itheima.patterns.behaviorpattern.state.after;
//电梯运行状态类
public class RunningState extends LiftState {
//运行的时候开电梯门?你疯了!电梯不会给你开的
@Override
public void open() {
//do nothing
}
//电梯门关闭?这是肯定了
@Override
public void close() {//虽然可以关门,但这个动作不归我执行
//do nothing
}
//这是在运行状态下要实现的方法
@Override
public void run() {
System.out.println("电梯正在运行...");
}
//这个事绝对是合理的,光运行不停止还有谁敢做这个电梯?!估计只有上帝了
@Override
public void stop() {
super.context.setLiftState(Context.STOPPING_STATE);
super.context.stop();
}
}
StoppingState.java(具体状态类)
package com.itheima.patterns.behaviorpattern.state.after;
//电梯停止状态类
public class StoppingState extends LiftState {
//停止状态,开门,那是要的!
@Override
public void open() {
//状态修改
super.context.setLiftState(Context.OPENING_STATE);
//动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作
super.context.getLiftState().open();
}
@Override
public void close() {//虽然可以关门,但这个动作不归我执行
//状态修改
super.context.setLiftState(Context.CLOSING_STATE);
//动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作
super.context.getLiftState().close();
}
//停止状态再跑起来,正常的很
@Override
public void run() {
//状态修改
super.context.setLiftState(Context.RUNNING_STATE);
//动作委托为CloseState来执行,也就是委托给了ClosingState子类执行这个动作
super.context.getLiftState().run();
}
//停止状态是怎么发生的呢?当然是停止方法执行了
@Override
public void stop() {
System.out.println("电梯停止了...");
}
}
Context.java(环境类)
package com.itheima.patterns.behaviorpattern.state.after;
//环境角色类
public class Context {
//定义对应状态对象的常量
public final static OpeningState OPENING_STATE = new OpeningState();
public final static ClosingState CLOSING_STATE = new ClosingState();
public final static RunningState RUNNING_STATE = new RunningState();
public final static StoppingState STOPPING_STATE = new StoppingState();
//定义一个当前电梯状态变量
private LiftState liftState;
public LiftState getLiftState() {
return liftState;
}
//设置当前状态对象
public void setLiftState(LiftState liftState) {
this.liftState = liftState;
//设置当前状态对象中的Context对象
this.liftState.setContext(this);
}
public void open() {
this.liftState.open();
}
public void close() {
this.liftState.close();
}
public void run() {
this.liftState.run();
}
public void stop() {
this.liftState.stop();
}
}
Client.java
package com.itheima.patterns.behaviorpattern.state.after;
public class Client {
public static void main(String[] args) {
//创建环境角色对象
Context context = new Context();
//设置当前电梯装填
context.setLiftState(new ClosingState());
context.open();
context.run();
context.close();
context.stop();
}
}
4.5.4.优缺点
(1)优点 ① 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。 ② 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。 (2)缺点 ① 状态模式的使用必然会增加系统类和对象的个数。 ② 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。 ③ 状态模式对"开闭原则"的支持并不太好。
4.5.5.使用场景(1)当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。 (2)一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。
4.6.观察者模式有关观察者模式的具体知识可以参考 Java 设计模式——观察者模式这篇文章。
4.7.中介者模式 4.7.1.概述(1)一般来说,同事类之间的关系是比较复杂的,多个同事类之间互相关联时,他们之间的关系会呈现为复杂的网状结构,这是一种过度耦合的架构,即不利于类的复用,也不稳定。例如在下左图中,有六个同事类对象,假如对象1发生变化,那么将会有4个对象受到影响。如果对象2发生变化,那么将会有5个对象受到影响。也就是说,同事类之间直接关联的设计是不好的。 (2)如果引入中介者模式,那么同事类之间的关系将变为星型结构,从下右图中可以看到,任何一个类的变动,只会影响的类本身,以及中介者,这样就减小了系统的耦合。一个好的设计,必定不会把所有的对象关系处理逻辑封装在本类中,而是使用一个专门的类来管理那些不属于自己的行为。 (3)中介者模式:又叫调停模式,定义一个中介角色来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。
中介者模式包含以下主要角色: (1)抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。 (2)具体中介者(ConcreteMediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。 (3)抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。 (4)具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。
4.7.3.案例实现【例】租房 现在租房基本都是通过房屋中介,房主将房屋托管给房屋中介,而租房者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者。其类图下: 具体实现代码如下: Mediator.java(抽象中介者)
package com.itheima.patterns.behaviorpattern.mediator;
//抽象中介者类
public abstract class Mediator {
public abstract void constact(String message,Person person);
}
Person.java(抽象同事类)
package com.itheima.patterns.behaviorpattern.mediator;
//抽象同事类
public abstract class Person {
protected String name;
protected Mediator mediator;
public Person(String name, Mediator mediator) {
this.name = name;
this.mediator = mediator;
}
}
Tenant.java(具体同事类)
package com.itheima.patterns.behaviorpattern.mediator;
//租房者
public class Tenant extends Person{
public Tenant(String name, Mediator mediator) {
super(name, mediator);
}
//和中介沟通的方法
public void constact(String message){
mediator.constact(message,this);
}
//获取信息
public void getMessage(String message){
System.out.println("租房者"+name+"获取到的信息是:"+message);
}
}
HouseOwner.java(具体同事类)
package com.itheima.patterns.behaviorpattern.mediator;
//房主类
public class HouseOwner extends Person{
public HouseOwner(String name, Mediator mediator) {
super(name, mediator);
}
//和中介沟通的方法
public void constact(String message){
mediator.constact(message,this);
}
//获取信息
public void getMessage(String message){
System.out.println("房主"+name+"获取到的信息是:"+message);
}
}
MediatorStructure.java(具体中介者)
package com.itheima.patterns.behaviorpattern.mediator;
//具体的中介者角色类
public class MediatorStructure extends Mediator{
//聚合房主和具体的租房者对象
private HouseOwner houseOwner;
private Tenant tenant;
public HouseOwner getHouseOwner() {
return houseOwner;
}
public void setHouseOwner(HouseOwner houseOwner) {
this.houseOwner = houseOwner;
}
public Tenant getTenant() {
return tenant;
}
public void setTenant(Tenant tenant) {
this.tenant = tenant;
}
@Override
public void constact(String message, Person person) {
if(person == houseOwner){
tenant.getMessage(message);
}else{
houseOwner.getMessage(message);
}
}
}
Client.java
package com.itheima.patterns.behaviorpattern.mediator;
public class Client {
public static void main(String[] args) {
//创建中介者对象
MediatorStructure mediator = new MediatorStructure();
//创建租房者对象
Tenant tenant = new Tenant("李四",mediator);
//创建房主对象
HouseOwner houseOwner = new HouseOwner("张三",mediator);
//中介者要知道具体的房主和租房者
mediator.setTenant(tenant);
mediator.setHouseOwner(houseOwner);
tenant.constact("我要租三室的房子!");
houseOwner.constact("我这里有三室的房子,你要租吗?");
}
}
结果如下:
房主张三获取到的信息是:我要租三室的房子!
租房者李四获取到的信息是:我这里有三室的房子,你要租吗?
4.7.4.优缺点
(1)优点 ① 松散耦合 中介者模式通过把多个同事对象之间的交互封装到中介者对象里面,从而使得同事对象之间松散耦合,基本上可以做到互补依赖。这样一来,同事对象就可以独立地变化和复用,而不再像以前那样“牵一处而动全身”了。 ② 集中控制交互 多个同事对象的交互,被封装在中介者对象里面集中管理,使得这些交互行为发生变化的时候,只需要修改中介者对象就可以了,当然如果是已经做好的系统,那么就扩展中介者对象,而各个同事类不需要做修改。 ③ 一对多关联转变为一对一的关联 没有使用中介者模式的时候,同事对象之间的关系通常是一对多的,引入中介者对象以后,中介者对象和同事对象的关系通常变成双向的一对一,这会让对象的关系更容易理解和实现。 (2)缺点 当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护。
4.7.5.使用场景(1)系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解。 (2)当想创建一个运行于多个类之间的对象,又不想生成新的子类时。
4.8.迭代器模式有关策略模式的具体知识可以参考 Java 设计模式——迭代器模式这篇文章。
4.9.访问者模式 4.9.1.概述访问者模式:封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。
4.9.2.结构访问者模式包含以下主要角色: (1)抽象访问者(Visitor)角色: 定义了对每一个元素 (Element) 访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数(Element的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变。 (2)具体访问者(ConcreteVisitor)角色: 给出对每一个元素类访问时所产生的具体行为。 (3)抽象元素(Element)角色: 定义了一个接受访问者的方法( accept ),其意义是指,每一个元素都要可以被访问者访问。 (4)具体元素(ConcreteElement)角色: 提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。 (5)对象结构(Object Structure)角色: 定义当中所提到的对象结构,对象结构是一个抽象表述,具体点可以理解为一个具有容器性质或者复合对象特性的类,它会含有一组元素( Element ),并且可以迭代这些元素,供访问者访问。
4.9.2.案例实现【例】给宠物喂食 现在养宠物的人特别多,我们就以这个为例,当然宠物还分为狗,猫等,要给宠物喂食的话,主人可以喂,其他人也可以喂食。类图如下: 具体实现代码如下: Person.java(抽象访问者)
package com.itheima.patterns.behaviorpattern.visitor;
//抽象访问者角色接口
public interface Person {
//给宠物猫喂食
void feed(Cat cat);
//给宠物狗喂食
void feed(Dog dog);
}
Animal.java(抽象元素)
package com.itheima.patterns.behaviorpattern.visitor;
//抽象元素角色类
public interface Animal {
//接受访问者访问的功能
void accept(Person person);
}
Cat.java(具体元素)
package com.itheima.patterns.behaviorpattern.visitor;
//具体元素角色类(宠物猫)
public class Cat implements Animal{
@Override
public void accept(Person person) {
//访问者给宠物猫喂食
person.feed(this);
System.out.println("宠物猫接受喂食");
}
}
Dog.java(具体元素)
package com.itheima.patterns.behaviorpattern.visitor;
//具体元素角色类(宠物狗)
public class Dog implements Animal{
@Override
public void accept(Person person) {
//访问者给宠物狗喂食
person.feed(this);
System.out.println("宠物狗接受喂食");
}
}
Owner.java(具体访问者)
package com.itheima.patterns.behaviorpattern.visitor;
//具体访问者角色类(宠物主人)
public class Owner implements Person{
@Override
public void feed(Cat cat) {
System.out.println("主人给猫喂食");
}
@Override
public void feed(Dog dog) {
System.out.println("主人给狗喂食");
}
}
SomeOne.java(具体访问者)
package com.itheima.patterns.behaviorpattern.visitor;
//具体访问者角色类(其他人)
public class SomeOne implements Person{
@Override
public void feed(Cat cat) {
System.out.println("其他人给猫喂食");
}
@Override
public void feed(Dog dog) {
System.out.println("其他人给猫喂食");
}
}
Home.java(对象结构)
package com.itheima.patterns.behaviorpattern.visitor;
import java.util.ArrayList;
import java.util.List;
//对象结构类
public class Home {
//声明一个集合对象,用来存储元素对象
private List nodeList = new ArrayList();
//添加元素
public void add(Animal animal){
nodeList.add(animal);
}
public void action(Person person){
//遍历集合,获取每一个元素,让访问者访问每一个元素
for (Animal animal : nodeList) {
animal.accept(person);
}
}
}
Client.java
package com.itheima.patterns.behaviorpattern.visitor;
public class Client {
public static void main(String[] args) {
//创建Home对象
Home home = new Home();
//添加元素到home对象中
home.add(new Dog());
home.add(new Cat());
//创建主人对象
Owner owner = new Owner();
//让主人喂食所有的宠物
home.action(owner);
System.out.println("===============");
//创建其他人对象
SomeOne someOne = new SomeOne();
//让其他人喂食所有的宠物
home.action(someOne);
}
}
结果如下:
主人给狗喂食
宠物狗接受喂食
主人给猫喂食
宠物猫接受喂食
===============
其他人给猫喂食
宠物狗接受喂食
其他人给猫喂食
宠物猫接受喂食
4.9.3.优缺点
(1)优点
扩展性好在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。复用性好通过访问者来定义整个对象结构通用的功能,从而提高复用程度。分离无关行为通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。(2)缺点对象结构变化很困难在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。––违反了依赖倒置原则访问者模式依赖了具体类,而没有依赖抽象类。 4.9.4.使用场景(1)对象结构相对稳定,但其操作算法经常变化的程序。 (2)对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
4.9.5.扩展事实上,访问者模式用到了一种名为双分派的技术。 (1)分派 变量被声明时的类型叫做变量的静态类型,有些人又把静态类型叫做明显类型;而变量所引用的对象的真实类型又叫做变量的实际类型。比如 Map map = new HashMap() ,map变量的静态类型是 Map,实际类型是 HashMap 。根据对象的类型而对方法进行的选择,就是分派(Dispatch),分派(Dispatch)又分为两种,即静态分派和动态分派。 ① 静态分派(Static Dispatch) 发生在编译时期,分派根据静态类型信息发生。静态分派对于我们来说并不陌生,方法重载就是静态分派。 ② 动态分派(Dynamic Dispatch) 发生在运行时期,动态分派动态地置换掉某个方法。Java通过方法的重写支持动态分派。 (2)动态分配 通过方法的重写支持动态分派。
public class Animal {
public void execute() {
System.out.println("Animal");
}
}
public class Dog extends Animal {
@Override public void execute() {
System.out.println("dog");
}
}
public class Cat extends Animal {
@Override public void execute() {
System.out.println("cat");
}
}
public class Client {
public static void main(String[] args) {
Animal a = new Dog();
a.execute();
Animal a1 = new Cat();
a1.execute();
}
}
运行结果如下:
dog
cat
上面代码的结果大家应该很容易想到,这不就是多态吗!运行执行的是子类中的方法。Java编译器在编译时期并不总是知道哪些代码会被执行,因为编译器仅仅知道对象的静态类型,而不知道对象的真实类型;而方法的调用则是根据对象的真实类型,而不是静态类型。 (3)静态分配 通过方法重载支持静态分派。
public class Animal {
}
public class Dog extends Animal {
}
public class Cat extends Animal {
}
public class Execute {
public void execute(Animal a) {
System.out.println("Animal");
}
public void execute(Dog d) {
System.out.println("dog");
}
public void execute(Cat c) {
System.out.println("cat");
}
}
public class Client {
public static void main(String[] args) {
Animal a = new Animal();
Animal a1 = new Dog();
Animal a2 = new Cat();
Execute exe = new Execute();
exe.execute(a);
exe.execute(a1);
exe.execute(a2);
}
}
运行结果如下:
animal
animal
animal
这个结果可能出乎一些人的意料了,为什么呢?因为重载方法的分派是根据静态类型进行的,这个分派过程在编译时期就完成了。 (4)双分派 所谓双分派技术就是在选择一个方法的时候,不仅仅要根据消息接收者(receiver)的运行时区别,还要根据参数的运行时区别。
public class Animal {
public void accept(Execute exe) {
exe.execute(this);
}
}
public class Dog extends Animal {
public void accept(Execute exe) {
exe.execute(this);
}
}
public class Cat extends Animal {
public void accept(Execute exe) {
exe.execute(this);
}
}
public class Execute {
public void execute(Animal a) {
System.out.println("animal");
}
public void execute(Dog d) {
System.out.println("dog");
}
public void execute(Cat c) {
System.out.println("cat");
}
}
public class Client {
public static void main(String[] args) {
Animal a = new Animal();
Animal d = new Dog();
Animal c = new Cat();
Execute exe = new Execute();
a.accept(exe);
d.accept(exe);
c.accept(exe);
}
}
运行结果如下:
animal
dog
cat
① 在上面代码中,客户端将Execute对象做为参数传递给Animal类型的变量调用的方法,这里完成第一次分派,这里是方法重写,所以是动态分派,也就是执行实际类型中的方法,同时也将自己 this 作为参数传递进去,这里就完成了第二次分派 ,这里的Execute类中有多个重载的方法,而传递进行的是 this,就是具体的实际类型的对象。 ② 说到这里,我们已经明白双分派是怎么回事了,但是它有什么效果呢?就是可以实现方法的动态绑定,我们可以对上面的程序进行修改。 ③ 双分派实现动态绑定的本质,就是在重载方法委派的前面加上了继承体系中覆盖的环节,由于覆盖是动态的,所以重载就是动态的了。
4.10.备忘录模式 4.10.1.概述(1)备忘录模式:又叫快照模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。 (2)备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原,很多软件都提供了撤销(Undo)操作,如 Word、记事本、Photoshop、IDEA等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在浏览器中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。
4.10.2.结构备忘录模式的主要角色如下: (1)发起人(Originator)角色: 记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。 (2)备忘录(Memento)角色: 负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。 (3)管理者(Caretaker)角色: 对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。 除此之外,备忘录有两个等效的接口: 窄接口: 管理者(Caretaker)对象(和其他发起人对象之外的任何对象)看到的是备忘录的窄接口(narror Interface),这个窄接口只允许他把备忘录对象传给其他的对象。 宽接口: 与管理者看到的窄接口相反,发起人对象可以看到一个宽接口(wide Interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。
4.10.3.案例实现【例】游戏挑战BOSS 游戏中的某个场景,一游戏角色有生命力、攻击力、防御力等数据,在打Boss前和后一定会不一样的,我们允许玩家如果感觉与Boss决斗的效果不理想可以让游戏恢复到决斗之前的状态。 (1)“白箱”备忘录模式 备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对所有对象公开。类图如下: 具体实现代码如下: RoleStateMemento.java(备忘录角色)
package com.itheima.patterns.behaviorpattern.memento.whitebox;
//备忘录角色类(存储历史状态)
public class RoleStateMemento {
//生命力
private int vit;
//攻击力
private int atk;
//防御力
private int def;
public RoleStateMemento() {
}
public RoleStateMemento(int vit, int atk, int def) {
this.vit = vit;
this.atk = atk;
this.def = def;
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
}
GameRole.java(发起人角色)
package com.itheima.patterns.behaviorpattern.memento.whitebox;
//游戏角色类(发起人角色)
public class GameRole {
//生命力
private int vit;
//攻击力
private int atk;
//防御力
private int def;
//初始化内部状态
public void initState(){
this.vit = 100;
this.atk = 100;
this.def = 100;
}
//战斗
public void fight(){
this.vit = 0;
this.atk = 0;
this.def = 0;
}
//保存角色状态功能
public RoleStateMemento saveState(){
return new RoleStateMemento(vit,atk,def);
}
//恢复角色状态
public void recoverState(RoleStateMemento roleStateMemento){
//将备忘录对象中存储的状态赋值给当前对象成员
this.vit = roleStateMemento.getVit();
this.atk = roleStateMemento.getAtk();
this.def = roleStateMemento.getDef();
}
//展示状态功能
public void stateDisplay(){
System.out.println("角色生命力:" + vit);
System.out.println("角色攻击力:" + atk);
System.out.println("角色防御力:" + def);
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
}
RoleStateCaretaker.java(管理者角色)
package com.itheima.patterns.behaviorpattern.memento.whitebox;
//备忘录对象管理对象
public class RoleStateCaretaker {
//声明RoleStateMemento类型的变量
private RoleStateMemento roleStateMemento;
public RoleStateMemento getRoleStateMemento() {
return roleStateMemento;
}
public void setRoleStateMemento(RoleStateMemento roleStateMemento) {
this.roleStateMemento = roleStateMemento;
//白箱
//roleStateMemento.setAtk(100);
}
}
Client.java
package com.itheima.patterns.behaviorpattern.memento.whitebox;
public class Client {
public static void main(String[] args) {
System.out.println("---------------大战boos前-----------------");
//创建游戏角色对象
GameRole gameRole = new GameRole();
gameRole.initState();//初始化状态操作
gameRole.stateDisplay();
//将该游戏角色内部状态进行备份
//创建管理者对象
RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
roleStateCaretaker.setRoleStateMemento(gameRole.saveState());
System.out.println("---------------大战boos后-----------------");
//损耗严重
gameRole.fight();
gameRole.stateDisplay();
System.out.println("---------------恢复之前的状态-----------------");
gameRole.recoverState(roleStateCaretaker.getRoleStateMemento());
gameRole.stateDisplay();
}
}
结果如下:
---------------大战boos前-----------------
角色生命力:100
角色攻击力:100
角色防御力:100
---------------大战boos后-----------------
角色生命力:0
角色攻击力:0
角色防御力:0
---------------恢复之前的状态-----------------
角色生命力:100
角色攻击力:100
角色防御力:100
分析:白箱备忘录模式是破坏封装性的。但是通过程序员自律,同样可以在一定程度上实现模式的大部分用意。
(2)“黑箱”备忘录模式 备忘录角色对发起人对象提供一个宽接口,而为其他对象提供一个窄接口。在Java语言中,实现双重接口的办法就是将备忘录类设计成发起人类的内部成员类。 将 RoleStateMemento 设为 GameRole 的内部类,从而将 RoleStateMemento 对象封装在GameRole 里面;在外面提供一个标识接口 Memento 给 RoleStateCaretaker 及其他对象使用。这样 GameRole 类看到的是 RoleStateMemento 所有的接口,而 RoleStateCaretaker 及其他对象看到的仅仅是标识接口 Memento 所暴露出来的接口,从而维护了封装型。其类图如下: 具体实现代码如下: Memento.java(备忘录接口)
package com.itheima.patterns.behaviorpattern.memento.blackbox;
//备忘录接口,对外提供窄接口
public interface Memento {
}
GameRole.java(发起人角色)
package com.itheima.patterns.behaviorpattern.memento.blackbox;
//游戏角色类(属于发起人角色)
public class GameRole {
private int vit; //生命力
private int atk; //攻击力
private int def; //防御力
//初始化内部状态
public void initState() {
this.vit = 100;
this.atk = 100;
this.def = 100;
}
//战斗
public void fight() {
this.vit = 0;
this.atk = 0;
this.def = 0;
}
//保存角色状态功能
public Memento saveState() {
return new RoleStateMemento(vit,atk,def);
}
//恢复角色状态
public void recoverState(Memento memento) {
RoleStateMemento roleStateMemento = (RoleStateMemento) memento;
//将备忘录对象中存储的状态赋值给当前对象的成员
this.vit = roleStateMemento.getVit();
this.atk = roleStateMemento.getAtk();
this.def = roleStateMemento.getDef();
}
//展示状态功能
public void stateDisplay() {
System.out.println("角色生命力:" + vit);
System.out.println("角色攻击力:" + atk);
System.out.println("角色防御力:" + def);
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
//在内部定义备忘录内部类 RoleStateMemento (该内部类设置为私有的)
private class RoleStateMemento implements Memento {
private int vit; //生命力
private int atk; //攻击力
private int def; //防御力
public RoleStateMemento(int vit, int atk, int def) {
this.vit = vit;
this.atk = atk;
this.def = def;
}
public RoleStateMemento() {
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
}
}
RoleStateCaretaker.java(管理者角色)
package com.itheima.patterns.behaviorpattern.memento.blackbox;
//备忘录对象管理对象
public class RoleStateCaretaker {
/*
负责人角色类 RoleStateCaretaker 能够得到的备忘录对象是以 Memento 为接口的,由于
这个接口仅仅是一个标识接口,因此负责人角色不可能改变这个备忘录对象的内容。
*/
//声明RoleStateMemento类型的变量
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
package com.itheima.patterns.behaviorpattern.memento.blackbox;
public class Client {
public static void main(String[] args) {
System.out.println("---------------大战boos前-----------------");
//创建游戏角色对象
GameRole gameRole = new GameRole();
gameRole.initState();//初始化状态操作
gameRole.stateDisplay();
//将该游戏角色内部状态进行备份
//创建管理者对象
RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
roleStateCaretaker.setMemento(gameRole.saveState());
System.out.println("---------------大战boos后-----------------");
//损耗严重
gameRole.fight();
gameRole.stateDisplay();
System.out.println("---------------恢复之前的状态-----------------");
gameRole.recoverState(roleStateCaretaker.getMemento());
gameRole.stateDisplay();
}
}
结果与“黑箱”备忘录模式的一样。
4.10.4.优缺点(1)优点 ① 提供了一种可以恢复状态的机制。当用户需要时能够方便地将数据恢复到某个历史的状态。 ② 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。 ③ 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。 (2)缺点 如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。
4.10.5.使用场景(1)需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。 (2)需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,idea等软件在编辑时按Ctrl+Z 组合键,还有数据库中事务操作。
4.11.解释器模式 4.11.1概述(1)如下图,设计一个软件用来进行加减计算。我们第一想法可能就是使用工具类,提供对应的加法和减法的工具方法。
//用于两个整数相加
public static int add(int a,int b){
return a + b;
}
//用于两个整数相加
public static int add(int a,int b,int c){
return a + b + c;
}
//用于n个整数相加
public static int add(Integer ... arr) {
int sum = 0;
for (Integer i : arr) {
sum += i;
}
return sum;
}
上面的形式比较单一、有限,如果形式变化非常多,这就不符合要求,因为加法和减法运算,两个运算符与数值可以有无限种组合方式。比如 1+2+3+4+5、1+2+3-4等等。显然,现在需要一种翻译识别机器,能够解析由数字以及 + - 符号构成的合法的运算序列。如果把运算符和数字都看作节点的话,能够逐个节点的进行读取解析运算,这就是解释器模式的思维。 (2)解释器模式:给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。 (3)在解释器模式中,我们需要将待解决的问题,提取出规则,抽象为一种“语言”。比如加减法运算,规则为:由数值和±符号组成的合法序列,“1+3-2” 就是这种语言的句子。解释器就是要解析出来语句的含义。但是如何描述规则呢? (4)文法/语法规则(用于描述语言的语法结构的形式规则):
expression ::= value | plus | minus
plus ::= expression '+' expression
minus ::= expression '-' expression
value ::= integer
注意: 这里的符号“::=”表示“定义为”的意思,竖线 | 表示或,左右的其中一个,引号内为字符本身,引号外为语法。 上面规则描述为 :表达式可以是一个值,也可以是plus或者minus运算,而plus和minus又是由表达式结合运算符构成,值的类型为整型数。 (5)抽象语法树 在计算机科学中,抽象语法树(AbstractSyntaxTree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。用树形来表示符合文法规则的句子:
解释器模式包含以下主要角色: (1)抽象表达式(Abstract Expression)角色: 定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。 (2)终结符表达式(Terminal Expression)角色: 是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。 (3)非终结符表达式(Nonterminal Expression)角色: 也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。 (4)环境(Context)角色: 通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。 (5)客户端(Client): 主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
4.11.3.案例实现【例】设计实现加减法的软件 具体实现代码如下: AbstractExpression.java(抽象表达式角色)
package com.itheima.patterns.behaviorpattern.interpreter;
//抽象表达式类
public abstract class AbstractExpression {
public abstract int interpret(Context context);
}
Variable.java
package com.itheima.patterns.behaviorpattern.interpreter;
//用于封装变量的类
public class Variable extends AbstractExpression{
//声明存储变量名的成员变量
private String name;
public Variable(String name) {
this.name = name;
}
@Override
public int interpret(Context context) {
//直接返回变量的值
return context.getValue(this);
}
@Override
public String toString() {
return name;
}
}
Context.java(环境角色)
package com.itheima.patterns.behaviorpattern.interpreter;
import java.util.HashMap;
import java.util.Map;
//环境角色类
public class Context {
//定义一个map集合,用来存储变量以及对应的值
private Map map = new HashMap();
//添加变量的功能
public void assign(Variable var,Integer value){
map.put(var,value);
}
//根据变量获取对应的值
public int getValue(Variable var){
return map.get(var);
}
}
Plus.java
package com.itheima.patterns.behaviorpattern.interpreter;
//加法表达式类
public class Plus extends AbstractExpression{
//+左边的表达式
private AbstractExpression left;
//+右边的表达式
private AbstractExpression right;
public Plus(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Context context) {
//将左边表达式的结果和右边的进行相加 2+3
return left.interpret(context) + right.interpret(context);
}
@Override
public String toString() {
return "(" + left.toString() + "+" + right.toString() + ")";
}
}
Minus.java
package com.itheima.patterns.behaviorpattern.interpreter;
//减法表达式类
public class Minus extends AbstractExpression{
//-左边的表达式
private AbstractExpression left;
//-右边的表达式
private AbstractExpression right;
public Minus(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Context context) {
//将左边表达式的结果和右边的进行相减 2-3
return left.interpret(context) - right.interpret(context);
}
@Override
public String toString() {
return "(" + left.toString() + "-" + right.toString() + ")";
}
}
Client.java(客户端)
package com.itheima.patterns.behaviorpattern.interpreter;
public class Client {
public static void main(String[] args){
//创建环境对象
Context context = new Context();
//创建多个变量对象
Variable a = new Variable("a");
Variable b = new Variable("b");
Variable c = new Variable("c");
Variable d = new Variable("d");
//将变量存储到环境变量中
context.assign(a,1);
context.assign(b,2);
context.assign(c,3);
context.assign(d,4);
//获取抽象语法树 a + b - c + d
AbstractExpression expression = new Minus(a,new Plus(new Minus(b,c),d));
//解释(计算)
int result = expression.interpret(context);
System.out.println(expression + "=" + result);
}
}
结果如下:
(a-((b-c)+d))=-2
4.11.4.优缺点
(1)优点 ① 易于改变和扩展文法 由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。 ② 实现文法较为容易 在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂。 ③ 增加新的解释表达式较为方便 如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合 “开闭原则”。 (2)缺点 ① 对于复杂文法难以维护 在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护。 ② 执行效率较低 由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。
4.11.5.使用场景(1)当语言的文法较为简单,且执行效率不是关键问题时。 (2)当问题重复出现,且可以用一种简单的语言来进行表达时。 (3)当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候。