23种设计模式学习笔记(1)
https://blog.csdn.net/qq_51495235/article/details/115358623
23种设计模式学习笔记(2)
https://blog.csdn.net/qq_51495235/article/details/115358846
23种设计模式学习笔记(3)
https://blog.csdn.net/qq_51495235/article/details/115359128
23种设计模式学习笔记(4)
https://blog.csdn.net/qq_51495235/article/details/115359246
23种设计模式学习笔记(5)
https://blog.csdn.net/qq_51495235/article/details/115359272
21,观察者模式
21.1 观察者模式的定义和特点
观察者(Observer)模式的定义:**指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。**这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
观察者模式是一种对象行为型模式,其主要优点如下。
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
- 目标与观察者之间建立了一套触发机制。
它的主要缺点如下。
- 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
- 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
21.2 观察者模式的结构与实现
实现观察者模式时要注意具体目标对象和具体观察者对象之间不能直接调用,否则将使两者之间紧密耦合起来,这违反了面向对象的设计原则
21.2.1 观察者模式的结构
- 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
- 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
- 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
- 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
21.2.1 代码实现
在使用微信公众号时,大家都会有这样的体验,当你关注的公众号中有新内容更新的话,它就会推送给关注公众号的微信用户端。我们使用观察者模式来模拟这样的场景,微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号
关系类图
Observer
package com.zhuang.observer;
/**
* @Classname Observer
* @Description 抽象观察者
* @Date 2021/3/28 14:14
* @Created by dell
*/
public interface Observer {
//更新的方法
void update(String messages);
}
WexinUser
package com.zhuang.observer;
/**
* @Classname WexinUser
* @Description 具体观察者类 实现更新的方法
* @Date 2021/3/28 14:14
* @Created by dell
*/
public class WexinUser implements Observer {
//用户名
private String name;
public WexinUser(String name) {
this.name = name;
}
public WexinUser() {
}
@Override
public void update(String messages) {
System.out.println(name + "-->" + messages);
}
}
Subject
package com.zhuang.observer;
/**
* @Classname Subject
* @Description 抽象主题类
* @Date 2021/3/28 14:15
* @Created by dell
*/
public interface Subject {
//增加订阅者
public void attach(Observer observer);
//删除订阅者
public void remove(Observer observer);
//通知订阅者更新消息
public void notify(String messages);
}
SubscriptionSubject
package com.zhuang.observer;
import java.util.ArrayList;
import java.util.List;
/**
* @Classname SubscriptionSubject
* @Description 具体主题(具体被观察者)
* @Date 2021/3/28 14:15
* @Created by dell
*/
public class SubscriptionSubject implements Subject {
//存储订阅公众号的微信用户
private List<Observer> weixinUserList = new ArrayList<Observer>();
@Override
public void attach(Observer observer) {
weixinUserList.add(observer);
}
@Override
public void remove(Observer observer) {
weixinUserList.remove(observer);
}
@Override
public void notify(String messages) {
for (Observer observer : weixinUserList) {
observer.update(messages);
}
}
}
Client
package com.zhuang.observer;
/**
* @Classname Client
* @Description 观察者模式 测试类
* @Date 2021/3/28 14:16
* @Created by dell
*/
public class Client {
public static void main(String[] args) {
SubscriptionSubject subject = new SubscriptionSubject();
//创建微信用户
WexinUser user1 = new WexinUser("张三");
WexinUser user2 = new WexinUser("李四");
WexinUser user3 = new WexinUser("王五");
//订阅公众号
subject.attach(user1);
subject.attach(user2);
subject.attach(user3);
//通过订阅用户
subject.notify("您关注的公众号更新啦~~~");
}
}
20.3 观察者模式应用场景
- 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
- 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
- 实现类似广播机制的功能,不需要知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播。
- 多层级嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知。
20.4 JDK源码解析
在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。
1,Observable类
Observable 类是抽象目标类(被观察者),它有一个 Vector 集合成员变量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。
-
void addObserver(Observer o) 方法:用于将新的观察者对象添加到集合中。
-
void notifyObservers(Object arg) 方法:调用集合中的所有观察者对象的 update方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知。
-
void setChange() 方法:用来设置一个 boolean 类型的内部标志,注明目标对象发生了变化。当它为true时,notifyObservers() 才会通知观察者。
2,Observer 接口
Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 update 方法,进行相应的工作。
【例】警察抓小偷
警察抓小偷也可以使用观察者模式来实现,警察是观察者,小偷是被观察者。代码如下:
小偷是一个被观察者,所以需要继承Observable类
package com.zhuang.observer.observable_example;
import java.util.Observable;
/**
* @Classname Thief
* @Description 小偷类 继承Observable接口
* @Date 2021/3/28 14:36
* @Created by dell
*/
public class Thief extends Observable {
private String name;
public Thief(String name) {
this.name = name;
}
public Thief() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void steal() {
System.out.println("小偷:我偷东西,有没有人来抓我!!!");
super.setChanged();//默认为true
super.notifyObservers();
}
}
警察是一个观察者,所以需要让其实现Observer接口
package com.zhuang.observer.observable_example;
import java.util.Observable;
import java.util.Observer;
/**
* @Classname Policemen
* @Description 警察类 实现Observe类 实现update方法
* @Date 2021/3/28 14:36
* @Created by dell
*/
public class Policemen implements Observer {
private String name;
public Policemen(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Policemen() {
}
@Override
public void update(Observable o, Object arg) {
System.out.println("警察:" + ((Thief) o).getName() + "你被我抓到了哈哈哈哈!!!");
}
}
客户端代码
package com.zhuang.observer.observable_example;
/**
* @Classname Client
* @Description 测试类
* @Date 2021/3/28 14:36
* @Created by dell
*/
public class Client {
public static void main(String[] args) {
//小偷对象
Thief thief = new Thief("法外狂徒张三");
//警察对象
Policemen policemen = new Policemen("小庄警察");
//警察盯着小偷
thief.addObserver(policemen);
//小偷行窃
thief.steal();
}
}
22,中介者模式
一般来说,同事类之间的关系是比较复杂的,多个同事类之间互相关联时,他们之间的关系会呈现为复杂的网状结构,这是一种过度耦合的架构,即不利于类的复用,也不稳定。例如在下左图中,有六个同事类对象,假如对象1发生变化,那么将会有4个对象受到影响。如果对象2发生变化,那么将会有5个对象受到影响。也就是说,同事类之间直接关联的设计是不好的。
如果引入中介者模式,那么同事类之间的关系将变为星型结构,从下右图中可以看到,任何一个类的变动,只会影响的类本身,以及中介者,这样就减小了系统的耦合。一个好的设计,必定不会把所有的对象关系处理逻辑封装在本类中,而是使用一个专门的类来管理那些不属于自己的行为。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oKpoFA7c-1617192259290)(C:\Users\dell\Desktop\Notes\设计模式\img\Snipaste_2021-03-30_23-23-04.png)]
22.1 中介者模式的定义和特点
中介者(Mediator)模式的定义:定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。
中介者模式是一种对象行为型模式,其主要优点如下。
- 类之间各司其职,符合迪米特法则。
- 降低了对象之间的耦合性,使得对象易于独立地被复用。
- 将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。
其主要缺点是:中介者模式将原本多个对象直接的相互依赖变成了中介者和多个同事类的依赖关系。当同事类越多时,中介者就会越臃肿,变得复杂且难以维护。
22.2 中介者模式的结构与实现
22.2.1 中介者模式的结构
- 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
- 具体中介者(ConcreteMediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
- 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
- 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。
22.2.2 代码实现
现在租房基本都是通过房屋中介,房主将房屋托管给房屋中介,而租房者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者
关系类图
Mediator
package com.zhuang.mediator;
/**
* @Classname Mediator
* @Description 抽象中介者
* @Date 2021/3/28 20:45
* @Created by dell
*/
public abstract class Mediator {
//声明一个联络方法
public abstract void constact(String message, Person person);
}
Person
package com.zhuang.mediator;
/**
* @Classname Person
* @Description 抽象同事类
* @Date 2021/3/28 20:45
* @Created by dell
*/
public abstract class Person {
protected String name;
protected Mediator mediator;
public Person(String name, Mediator mediator) {
this.name = name;
this.mediator = mediator;
}
}
HouseOwner
package com.zhuang.mediator;
/**
* @Classname HouseOwner
* @Description 具体同事类 房屋拥有者
* @Date 2021/3/28 20:45
* @Created by dell
*/
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);
}
}
Tenant
package com.zhuang.mediator;
/**
* @Classname Tenant
* @Description 具体同事类 承租人
* @Date 2021/3/28 20:46
* @Created by dell
*/
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);
}
}
MediatorStructure
package com.zhuang.mediator;
/**
* @Classname MediatorStructure
* @Description 中介机构
* @Date 2021/3/28 20:46
* @Created by dell
*/
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
package com.zhuang.mediator;
/**
* @Classname Client
* @Description 中介者模式 测试类
* @Date 2021/3/28 20:47
* @Created by dell
*/
public class Client {
public static void main(String[] args) {
//房主 租房者 中介机构
MediatorStructure mediator = new MediatorStructure();
//房主和租房者只需要知道中介机构即可
HouseOwner houseOwner = new HouseOwner("张三", mediator);
Tenant tenant = new Tenant("李四", mediator);
//中介机构需要知道房主和租房者
mediator.setHouseOwner(houseOwner);
mediator.setTenant(tenant);
tenant.constact("需要租一间房子");
houseOwner.constact("我有一间房子,你要租吗???");
}
}
22.3 中介者模式应用场景
- 当对象之间存在复杂的网状结构关系而导致依赖关系混乱且难以复用时。
- 当想创建一个运行于多个类之间的对象,又不想生成新的子类时。
22.4 中介者模式的注意事项和细节
- 多个类相互耦合,会形成网状结构,使用中介者模式将网状结构分离为星型结构,进行解耦
- 减少类间依赖,降低了耦合,符合迪米特法则
- 中介者承担较多责任,出问题,系统会受到影响
- 设计不当,实际使用,特别注意
23,备忘录模式
23.1 中介者模式的定义和特点
备忘录(Memento)模式的定义:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。**该模式又叫快照模式。
备忘录模式是一种对象行为型模式,其主要优点如下。
- 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
- 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
- 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
其主要缺点是:
- 资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。
23.2 备忘录模式的结构与实现
23.2.1 备忘录模式的结构
- 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
- 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
- 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
- 备忘录有两个等效的接口:
- 窄接口:管理者(Caretaker)对象(和其他发起人对象之外的任何对象)看到的是备忘录的窄接口(narror Interface),这个窄接口只允许他把备忘录对象传给其他的对象。
- 宽接口:与管理者看到的窄接口相反,发起人对象可以看到一个宽接口(wide Interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。
23.2.2 代码实现
游戏中的某个场景,一游戏角色有生命力、攻击力、防御力等数据,在打Boss前和后一定会不一样的,我们允许玩家如果感觉与Boss决斗的效果不理想可以让游戏恢复到决斗之前的状态。
要实现上述案例,有两种方式:
- “白箱”备忘录模式
- “黑箱”备忘录模式
23.2.2.1**“白箱”备忘录模式**
备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对所有对象公开
关系类图
GameRole
package com.zhuang.memento.white_box;
/**
* @Classname GameRole
* @Description 游戏角色类
* @Date 2021/3/29 10:14
* @Created by dell
*/
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;
}
}
RoleStateMemento
package com.zhuang.memento.white_box;
/**
* @Classname RoleStateMemento
* @Description 游戏状态存储类 备忘录类
* @Date 2021/3/29 10:14
* @Created by dell
*/
public class RoleStateMemento {
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 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
package com.zhuang.memento.white_box;
/**
* @Classname RoleStateCaretaker
* @Description 角色状态管理类
* @Date 2021/3/29 10:15
* @Created by dell
*/
public class RoleStateCaretaker {
private RoleStateMemento roleStateMemento;
public RoleStateMemento getRoleStateMemento() {
return roleStateMemento;
}
public void setRoleStateMemento(RoleStateMemento roleStateMemento) {
this.roleStateMemento = roleStateMemento;
}
}
Client
package com.zhuang.memento.white_box;
/**
* @Classname Client
* @Description 备忘录模式 白箱 测试类
* @Date 2021/3/29 10:15
* @Created by dell
*/
public class Client {
public static void main(String[] args) {
System.out.println("--------------大战Boss前------------------------");
//大战Boss前
GameRole gameRole = new GameRole();
gameRole.initState();
gameRole.stateDisplay();
//保存进度
RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
roleStateCaretaker.setRoleStateMemento(gameRole.saveState());
System.out.println("--------------大战Boss后------------------------");
//大战Boss 损耗严重
gameRole.fight();
gameRole.stateDisplay();
System.out.println("--------------满血复活------------------------");
gameRole.recoverState(roleStateCaretaker.getRoleStateMemento());
gameRole.stateDisplay();
}
}
23.2.2.2黑箱备忘录模式
备忘录角色对发起人对象提供一个宽接口,而为其他对象提供一个窄接口。在Java语言中,实现双重接口的办法就是将备忘录类设计成发起人类的内部成员类。
将 RoleStateMemento
设为 GameRole
的内部类,从而将 RoleStateMemento
对象封装在 GameRole
里面;在外面提供一个标识接口 Memento
给 RoleStateCaretaker
及其他对象使用。这样 GameRole
类看到的是 RoleStateMemento
所有的接口,而RoleStateCaretaker
及其他对象看到的仅仅是标识接口 Memento
所暴露出来的接口,从而维护了封装型。类图如下:
GameRole
package com.zhuang.memento.black_box;
import com.zhuang.memento.white_box.RoleStateMemento;
/**
* @Classname GameRole
* @Description 游戏角色类
* @Date 2021/3/29 10:14
* @Created by dell
*/
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 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;
}
}
}
Memento
package com.zhuang.memento.black_box;
/**
* @Classname Memento
* @Description 窄接口
* @Date 2021/3/29 10:40
* @Created by dell
*/
public interface Memento {
}
RoleStateCaretaker
package com.zhuang.memento.black_box;
/**
* @Classname RoleStateCaretaker
* @Description 角色状态管理类
* @Date 2021/3/29 10:15
* @Created by dell
*/
public class RoleStateCaretaker {
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
Client
package com.zhuang.memento.black_box;
/**
* @Classname Client
* @Description 备忘录模式 黑箱 测试类
* @Date 2021/3/29 10:15
* @Created by dell
*/
public class Client {
public static void main(String[] args) {
System.out.println("--------------大战Boss前------------------------");
//大战Boss前
GameRole gameRole = new GameRole();
gameRole.initState();
gameRole.stateDisplay();
//保存进度
RoleStateCaretaker roleStateCaretaker = new RoleStateCaretaker();
roleStateCaretaker.setMemento(gameRole.saveState());
System.out.println("--------------大战Boss后------------------------");
//大战Boss 损耗严重
gameRole.fight();
gameRole.stateDisplay();
System.out.println("--------------满血复活------------------------");
gameRole.recoverState(roleStateCaretaker.getMemento());
gameRole.stateDisplay();
}
}
23.3 备忘录模式应用场景
- 需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。
- 需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,idea等软件在编辑时按 Ctrl+Z 组合键,还有数据库中事务操作。
23.4 备忘录模式的注意事项和细节
- 给用户提供一种可以恢复的机制,可以使用户能够比较方便回到某个历史的状态
- 实现了信息的封装,使用户不需要关心保存细节
- 类成员变量过多,占用比较大的资源,每一次保存都会消耗一定内存
24,解释器模式
设计一个软件用来进行加减计算。我们第一想法就是使用工具类,提供对应的加法和减法的工具方法。
//用于两个整数相加
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等等。
显然,现在需要一种翻译识别机器,能够解析由数字以及 + – 符号构成的合法的运算序列。如果把运算符和数字都看作节点的话,能够逐个节点的进行读取解析运算,这就是解释器模式的思维。
24.1 解释器模式的定义和特点
解释器(Interpreter)模式的定义:**给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。**也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。
这里提到的文法和句子的概念同编译原理中的描述相同,“文法”指语言的语法规则,而“句子”是语言集中的元素。例如,汉语中的句子有很多,“我是中国人”是其中的一个句子,可以用一棵语法树来直观地描述语言中的句子。
解释器模式是一种类行为型模式,其主要优点如下。
- 扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
- 容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。
解释器模式的主要缺点如下。
- 执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。
- 会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。
- 可应用的场景比较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到。
24.2 解释器模式的结构与实现
24.2.1 解释器模式的结构
- 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
- 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
- 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
- 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
- 客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
24.2.2 代码实现
关系类图
AbstractExpression
package com.zhuang.interpreter;
/**
* @Classname AbstractExpression
* @Description 抽象角色
* @Date 2021/3/31 9:45
* @Created by dell
*/
public abstract class AbstractExpression {
public abstract int interpret(Context context);
}
Value
package com.zhuang.interpreter;
/**
* @Classname Value
* @Description 终结符表达式角色
* @Date 2021/3/31 9:48
* @Created by dell
*/
public class Value extends AbstractExpression {
private int value;
public Value(int value) {
this.value = value;
}
@Override
public int interpret(Context context) {
return value;
}
@Override
public String toString() {
return new Integer(value).toString();
}
}
Plus
package com.zhuang.interpreter;
/**
* @Classname Plus
* @Description 非终结符表达式角色 加法表达式
* @Date 2021/3/31 9:46
* @Created by dell
*/
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) {
return left.interpret(context) + right.interpret(context);
}
@Override
public String toString() {
return "(" + left.toString() + " + " + right.toString() + ")";
}
}
Minus
package com.zhuang.interpreter;
/**
* @Classname Minus
* @Description 非终结符表达式角色 减法表达式
* @Date 2021/3/31 9:46
* @Created by dell
*/
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) {
return left.interpret(context) - right.interpret(context);
}
@Override
public String toString() {
return "(" + left.toString() + " - " + right.toString() + ")";
}
}
Variable
package com.zhuang.interpreter;
/**
* @Classname Variable
* @Description 终结符表达式角色 变量表达式
* @Date 2021/3/31 9:46
* @Created by dell
*/
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
package com.zhuang.interpreter;
import java.util.HashMap;
import java.util.Map;
/**
* @Classname Context
* @Description 环境类
* @Date 2021/3/31 9:46
* @Created by dell
*/
public class Context {
private Map<Variable, Integer> map = new HashMap<Variable, Integer>();
public void assign(Variable var, Integer value) {
map.put(var, value);
}
public int getValue(Variable var) {
Integer value = map.get(var);
return value;
}
}
Client
package com.zhuang.interpreter;
/**
* @Classname Client
* @Description 解释权模式 测试类
* @Date 2021/3/31 9:46
* @Created by dell
*/
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");
Variable e = new Variable("e");
context.assign(a, 2);
context.assign(b, 3);
context.assign(c, 4);
context.assign(d, 5);
context.assign(e, 6);
AbstractExpression expression = new Minus(new Plus(new Plus(new Plus(a, b), c), d), e);
System.out.println(expression + "=" + expression.interpret(context));
}
}
24.3 解释器模式使用场景
- 当语言的文法较为简单,且执行效率不是关键问题时。
- 当问题重复出现,且可以用一种简单的语言来进行表达时。
- 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/140736.html