前言
哈喽,大家好,我是janker。
今天这一篇应该是介绍设计模式的最后一篇,也是我学习设计模式的最后一篇。承接上篇文章我们今天继续介绍剩下的行为型设计模式,观察者模式、中介者模式、迭代器模式、访问者模式、备忘录模式、解释器模式。
观察者模式
在日常生活中,很多时候,一种事物都不是独立存在的,其中一个事物的行为发生改变可能会导致一个或者多个其他事物的行为也发生改变。例如,某种商品的物价上涨时会导致部分商家高兴,而消费者伤心;还有,当我们开车到交叉路口时,遇到红灯会停,遇到绿灯会行。这样的例子还有很多,例如,股票价格与股民、微信公众号与微信用户、气象局的天气预报与听众、小偷与警察等。
在软件世界也是这样,例如,Excel 中的数据与折线图、饼状图、柱状图之间的关系;MVC 模式中的模型与视图的关系;事件模型中的事件源与事件处理者。所有这些,如果用观察者模式来实现就非常方便。
定义与特点
观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
优点
-
降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。 -
目标与观察者之间建立了一套触发机制。
缺点
-
目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。 -
当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
结构与实现
实现观察者模式时要注意具体目标对象和具体观察者对象之间不能直接调用,否则将使两者之间紧密耦合起来,这违反了面向对象的设计原则。
模式的结构
观察者模式的主要角色如下。
-
抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。 -
具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。 -
抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。 -
具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
观察者模式的结构图如图所示。
模式的实现
1. 抽象目标类(Subject)
public abstract class Subject {
protected List<Observer> observers = new ArrayList<>();
public void add(Observer observer){
observers.add(observer);
}
public void remove(Observer observer){
observers.remove(observer);
}
public abstract void notifyObserver();
}
2. 具体目标类(ConcreteSubject)
public class ConcreteSubject extends Subject {
@Override
public void notifyObserver() {
System.out.println("具体目标发生改变...");
System.out.println("--------------");
for (Object obs : observers) {
((Observer) obs).response();
}
}
}
3. 抽象观察者类(Observer)
public interface Observer {
void response(); //反应
}
4. 具体观察者类(ConcreteObserver1)
public class ConcreteObserver1 implements Observer{
@Override
public void response() {
System.out.println("具体观察者1作出反应!");
}
}
5. 具体观察者类(ConcreteObserver2)
public class ConcreteObserver2 implements Observer{
@Override
public void response() {
System.out.println("具体观察者2作出反应!");
}
}
6. 观察者模式测试类(ObserverPatternTest)
public class ObserverPatternTest {
public static void main(String[] args) {
Subject subject = new ConcreteSubject();
Observer obs1 = new ConcreteObserver1();
Observer obs2 = new ConcreteObserver2();
subject.add(obs1);
subject.add(obs2);
subject.notifyObserver();
}
}
7. 测试结果
具体目标发生改变...
--------------
具体观察者1作出反应!
具体观察者2作出反应!
中介者模式
在现实生活中,常常会出现好多对象之间存在复杂的交互关系,这种交互关系常常是“网状结构”,它要求每个对象都必须知道它需要交互的对象。例如,每个人必须记住他(她)所有朋友的电话;而且,朋友中如果有人的电话修改了,他(她)必须让其他所有的朋友一起修改,这叫作“牵一发而动全身”,非常复杂。
如果把这种“网状结构”改为“星形结构”的话,将大大降低它们之间的“耦合性”,这时只要找一个“中介者”就可以了。如前面所说的“每个人必须记住所有朋友电话”的问题,只要在网上建立一个每个朋友都可以访问的“通信录”就解决了。这样的例子还有很多,例如,你刚刚参加工作想租房,可以找“房屋中介”;或者,自己刚刚到一个陌生城市找工作,可以找“人才交流中心”帮忙。
在软件的开发过程中,这样的例子也很多,例如,在 MVC
框架中,控制器(C
)就是模型(M
)和视图(V
)的中介者;还有大家常用的 QQ 聊天程序的“中介者”是 QQ 服务器。所有这些,都可以采用“中介者模式”来实现,它将大大降低对象之间的耦合性,提高系统的灵活性。
定义与特点
中介者(Mediator
)模式的定义:定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。
中介者模式是一种对象行为型模式。
优点
-
类之间各司其职,符合迪米特法则。 -
降低了对象之间的耦合性,使得对象易于独立地被复用。 -
将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。
缺点
中介者模式将原本多个对象直接的相互依赖变成了中介者和多个同事类的依赖关系。当同事类越多时,中介者就会越臃肿,变得复杂且难以维护。
结构与实现
中介者模式实现的关键是找出“中介者”,下面对它的结构和实现进行分析。
模式的结构
中介者模式包含以下主要角色。
-
抽象中介者( Mediator
)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。 -
具体中介者( Concrete Mediator
)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。 -
抽象同事类( Colleague
)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。 -
具体同事类( Concrete Colleague
)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。
中介者模式的结构图如图所示
模式的实现
1. 抽象中介者类(Mediator)
public abstract class Mediator {
public abstract void register(Colleague colleague);
public abstract void relay(Colleague cl); //转发
}
2. 具体中介者类(ConcreteMediator)
public class ConcreteMediator extends Mediator {
private List<Colleague> colleagues = new ArrayList<Colleague>();
@Override
public void register(Colleague colleague) {
if (!colleagues.contains(colleague)){
colleagues.add(colleague);
colleague.setMedium(this);
}
}
@Override
public void relay(Colleague cl) {
for (Colleague ob : colleagues) {
if (!ob.equals(cl)){
((Colleague)ob).receive();
}
}
}
}
3. 抽象同事类(Colleague)
public abstract class Colleague {
protected Mediator mediator;
public void setMedium(Mediator mediator){
this.mediator = mediator;
}
public abstract void receive();
public abstract void send();
}
4. 具体同事类1(ConcreteColleague1)
public class ConcreteColleague1 extends Colleague {
@Override
public void receive() {
System.out.println("具体同事类1收到请求。");
}
@Override
public void send() {
System.out.println("具体同事类1发出请求。");
mediator.relay(this); //请求中介者转发
}
}
5. 具体同事类2(ConcreteColleague2)
public class ConcreteColleague2 extends Colleague {
@Override
public void receive() {
System.out.println("具体同事类2收到请求。");
}
@Override
public void send() {
System.out.println("具体同事类2发出请求。");
mediator.relay(this); //请求中介者转发
}
}
6. 中介者测试类(MediatorPatternTest)
public class MediatorPatternTest {
public static void main(String[] args) {
Mediator md = new ConcreteMediator();
Colleague c1, c2;
c1 = new ConcreteColleague1();
c2 = new ConcreteColleague2();
md.register(c1);
md.register(c2);
c1.send();
System.out.println("-------------");
c2.send();
}
}
7. 运行结果
具体同事类1发出请求。
具体同事类2收到请求。
-------------
具体同事类2发出请求。
具体同事类1收到请求。
迭代器模式
现实生活以及程序设计中,经常要访问一个聚合对象中的各个元素,如数据结构中的链表遍历,通常的做法是将链表的创建和遍历都放在同一个类中,但这种方式不利于程序的扩展,如果要更换遍历方法就必须修改程序源代码,这违背了 “开闭原则”。
既然将遍历方法封装在聚合类中不可取,那么聚合类中不提供遍历方法,将遍历方法由用户自己实现是否可行呢?答案是同样不可取,因为这种方式会存在两个缺点:
-
暴露了聚合类的内部表示,使其数据不安全; -
增加了客户的负担。
“迭代器模式”能较好地克服以上缺点,它在客户访问类与聚合类之间插入一个迭代器,这分离了聚合对象与其遍历行为,对客户也隐藏了其内部细节,且满足“单一职责原则”和“开闭原则”,如 Java
中的 Collection
、List
、Set
、Map
等都包含了迭代器。
迭代器模式在生活中应用的比较广泛,比如:物流系统中的传送带,不管传送的是什么物品,都会被打包成一个个箱子,并且有一个统一的二维码。这样我们不需要关心箱子里是什么,在分发时只需要一个个检查发送的目的地即可。再比如,我们平时乘坐交通工具,都是统一刷卡或者刷脸进站,而不需要关心是男性还是女性、是残疾人还是正常人等信息。
定义与特点
迭代器(Iterator
)模式的定义:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。迭代器模式是一种对象行为型模式,其主要优点如下。
-
访问一个聚合对象的内容而无须暴露它的内部表示。 -
遍历任务交由迭代器完成,这简化了聚合类。 -
它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。 -
增加新的聚合类和迭代器类都很方便,无须修改原有代码。 -
封装性良好,为遍历不同的聚合结构提供一个统一的接口。
其主要缺点是:增加了类的个数,这在一定程度上增加了系统的复杂性。
在日常开发中,我们几乎不会自己写迭代器。除非需要定制一个自己实现的数据结构对应的迭代器,否则,开源框架提供的 API 完全够用。
结构与实现
迭代器模式是通过将聚合对象的遍历行为分离出来,抽象成迭代器类来实现的,其目的是在不暴露聚合对象的内部结构的情况下,让外部代码透明地访问聚合的内部数据。现在我们来分析其基本结构与实现方法。
模式的结构
迭代器模式主要包含以下角色。
-
抽象聚合( Aggregate
)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口。 -
具体聚合( ConcreteAggregate
)角色:实现抽象聚合类,返回一个具体迭代器的实例。 -
抽象迭代器( Iterator
)角色:定义访问和遍历聚合元素的接口,通常包含hasNext()
、first()
、next() 等方法。 -
具体迭代器( Concretelterator
)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
其结构如图所示
模式的实现
1. 抽象聚合类(Aggregate)
public interface Aggregate {
void add(Object obj);
void remove(Object obj);
Iterator getIterator();
}
2. 具体聚合类(ConcreteAggregate)
public class ConcreteAggregate implements Aggregate {
private List<Object> list = new ArrayList<Object>();
@Override
public void add(Object obj) {
list.add(obj);
}
@Override
public void remove(Object obj) {
list.remove(obj);
}
@Override
public Iterator getIterator() {
return new ConcreteIterator(list);
}
}
3. 抽象迭代器(Iterator)
public interface Iterator {
Object first();
Object next();
boolean hasNext();
}
4. 具体迭代器(ConcreteIterator)
public class ConcreteIterator implements Iterator {
private List<Object> list = null;
private int index = -1;
public ConcreteIterator(List<Object> list) {
this.list = list;
}
@Override
public Object first() {
index = 0;
return list.get(0);
}
@Override
public Object next() {
Object obj = null;
if (this.hasNext()){
obj = list.get(++index);
}
return obj;
}
@Override
public boolean hasNext() {
if (index < list.size() -1){
return true;
}else
return false;
}
}
5. 迭代器模式测试类(IteratorPatternTest)
public class IteratorPatternTest {
public static void main(String[] args) {
Aggregate ag = new ConcreteAggregate();
ag.add("张三");
ag.add("李四");
ag.add("王五");
System.out.println("聚合的内容有:");
Iterator it = ag.getIterator();
while (it.hasNext()) {
Object ob = it.next();
System.out.print(ob.toString() + "t");
}
Object ob = it.first();
System.out.println("nFirst:" + ob.toString());
}
}
6. 运行结果
聚合的内容有:
张三 李四 王五
First:张三
访问者模式
在现实生活中,有些集合对象存在多种不同的元素,且每种元素也存在多种不同的访问者和处理方式。例如,公园中存在多个景点,也存在多个游客,不同的游客对同一个景点的评价可能不同;医院医生开的处方单中包含多种药元素,査看它的划价员和药房工作人员对它的处理方式也不同,划价员根据处方单上面的药品名和数量进行划价,药房工作人员根据处方单的内容进行抓药。
这样的例子还有很多,例如,电影或电视剧中的人物角色,不同的观众对他们的评价也不同;还有顾客在商场购物时放在“购物车”中的商品,顾客主要关心所选商品的性价比,而收银员关心的是商品的价格和数量。
这些被处理的数据元素相对稳定而访问方式多种多样的数据结构,如果用“访问者模式”来处理比较方便。访问者模式能把处理方法从数据结构中分离出来,并可以根据需要增加新的处理方法,且不用修改原来的程序代码与数据结构,这提高了程序的扩展性和灵活性。
定义与特点
访问者(Visitor
)模式的定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
访问者(Visitor
)模式是一种对象行为型模式,其主要优点如下。
-
扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。 -
复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。 -
灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。 -
符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
访问者(Visitor
)模式的主要缺点如下。
-
增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。 -
破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。 -
违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。
结构与实现
访问者(Visitor
)模式实现的关键是如何将作用于元素的操作分离出来封装成独立的类,其基本结构与实现方法如下。
模式的结构
访问者模式包含以下主要角色。
-
抽象访问者( Visitor
)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作visit()
,该操作中的参数类型标识了被访问的具体元素。 -
具体访问者( ConcreteVisitor
)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。 -
抽象元素( Element
)角色:声明一个包含接受操作accept()
的接口,被接受的访问者对象作为accept()
方法的参数。 -
具体元素( ConcreteElement
)角色:实现抽象元素角色提供的accept()
操作,其方法体通常都是visitor.visit(this)
,另外具体元素中可能还包含本身业务逻辑的相关操作。 -
对象结构( Object Structure
)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由List
、Set
、Map
等聚合类实现。
其结构图如图所示。
模式的实现
1. 抽象访问者类(Visitor)
public interface Visitor {
void visit(ConcreteElementA element);
void visit(ConcreteElementB element);
}
2. 具体访问者类A(ConcreteVisitorA)
public class ConcreteVisitorA implements Visitor{
@Override
public void visit(ConcreteElementA element) {
System.out.println("具体访问者A访问-->" + element.operationA());
}
@Override
public void visit(ConcreteElementB element) {
System.out.println("具体访问者A访问-->" + element.operationB());
}
}
3. 具体访问者类B(ConcreteVisitorB)
public class ConcreteVisitorB implements Visitor {
@Override
public void visit(ConcreteElementA element) {
System.out.println("具体访问者A访问-->" + element.operationA());
}
@Override
public void visit(ConcreteElementB element) {
System.out.println("具体访问者A访问-->" + element.operationB());
}
}
4. 抽象元素类(Element)
public interface Element {
void accept(Visitor visitor);
}
5. 具体元素类A(ConcreteElementA)
public class ConcreteElementA implements Element{
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String operationA() {
return "具体元素A的操作。";
}
}
6. 具体元素类B(ConcreteElementA)
public class ConcreteElementB implements Element{
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String operationB() {
return "具体元素B的操作。";
}
}
7. 对象结构类(ObjectStructure)
public class ObjectStructure {
private List<Element> list = new ArrayList<>();
public void accept(Visitor visitor){
Iterator<Element> i = list.iterator();
while (i.hasNext()){
((Element)i.next()).accept(visitor);
}
}
public void add(Element element){
list.add(element);
}
public void remove(Element element){
list.remove(element);
}
}
8. 访问者模式测试类(VisitorPatternTest)
public class VisitorPatternTest {
public static void main(String[] args) {
ObjectStructure os = new ObjectStructure();
os.add(new ConcreteElementA());
os.add(new ConcreteElementB());
Visitor visitor = new ConcreteVisitorA();
os.accept(visitor);
System.out.println("------------------------");
visitor = new ConcreteVisitorB();
os.accept(visitor);
}
}
9. 运行结果
具体访问者A访问-->具体元素A的操作。
具体访问者A访问-->具体元素B的操作。
------------------------
具体访问者A访问-->具体元素A的操作。
具体访问者A访问-->具体元素B的操作。
备忘录模式
每个人都有犯错误的时候,都希望有种“后悔药”能弥补自己的过失,让自己重新开始,但现实是残酷的。在计算机应用中,客户同样会常常犯错误,能否提供“后悔药”给他们呢?当然是可以的,而且是有必要的。这个功能由“备忘录模式”来实现。
其实很多应用软件都提供了这项功能,如 Word
、记事本、Photoshop
、Eclipse
等软件在编辑时按 Ctrl+Z
组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 IE 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。
备忘录模式能记录一个对象的内部状态,当用户后悔时能撤销当前操作,使数据恢复到它原先的状态。
定义与特点
备忘录(Memento
)模式的定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。
备忘录模式是一种对象行为型模式,其主要优点如下。
-
提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。 -
实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。 -
简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
其主要缺点是:资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。
结构与实现
备忘录模式的核心是设计备忘录类以及用于管理备忘录的管理者类,现在我们来学习其结构与实现。
模式的结构
备忘录模式的主要角色如下。
-
发起人( Originator
)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。 -
备忘录( Memento
)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。 -
管理者( Caretaker
)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
备忘录模式的结构图如图
模式的实现
1. 备忘录(Memento)
public class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
2. 发起人(Originator)
public class Originator {
private String state;
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
public Memento createMemento() {
return new Memento(state);
}
public void restoreMemento(Memento m) {
this.setState(m.getState());
}
}
3. 管理者(Caretaker)
public class Caretaker {
private Memento memento;
public void setMemento(Memento m) {
memento = m;
}
public Memento getMemento() {
return memento;
}
}
4. 备忘录测试(MementoPatternTest)
public class MementoPatternTest {
public static void main(String[] args) {
Originator or = new Originator();
Caretaker cr = new Caretaker();
or.setState("S0");
System.out.println("初始状态:" + or.getState());
cr.setMemento(or.createMemento()); //保存状态
or.setState("S1");
System.out.println("新的状态:" + or.getState());
or.restoreMemento(cr.getMemento()); //恢复状态
System.out.println("恢复状态:" + or.getState());
}
}
5. 运行结果
初始状态:S0
新的状态:S1
恢复状态:S0
###小结
一般情况下关于游戏储存进度,归档等会使用这个设计模式,此外还有就是你的用户请求”撤销“,也会使用到备忘录模式。
解释器模式
在软件开发中,会遇到有些问题多次重复出现,而且有一定的相似性和规律性。如果将它们归纳成一种简单的语言,那么这些问题实例将是该语言的一些句子,这样就可以用“编译原理”中的解释器模式来实现了。
虽然使用解释器模式的实例不是很多,但对于满足以上特点,且对运行效率要求不是很高的应用实例,如果用解释器模式来实现,其效果是非常好的,本文将介绍其工作原理与使用方法。
定义与特点
解释器(Interpreter
)模式的定义:给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。
这里提到的文法和句子的概念同编译原理中的描述相同,“文法”指语言的语法规则,而“句子”是语言集中的元素。例如,汉语中的句子有很多,“我是中国人”是其中的一个句子,可以用一棵语法树来直观地描述语言中的句子。
解释器模式是一种类行为型模式
优点
-
扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。 -
容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。
缺点
-
执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。 -
会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。 -
可应用的场景比较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到。
结构与实现
解释器模式常用于对简单语言的编译或分析实例中,为了掌握好它的结构与实现,必须先了解编译原理中的“文法、句子、语法树”等相关概念。
1. 文法
文法是用于描述语言的语法结构的形式规则。没有规矩不成方圆,例如,有些人认为完美爱情的准则是“相互吸引、感情专一、任何一方都没有恋爱经历”,虽然最后一条准则较苛刻,但任何事情都要有规则,语言也一样,不管它是机器语言还是自然语言,都有它自己的文法规则。例如,中文中的“句子”的文法如下。
〈句子〉::=〈主语〉〈谓语〉〈宾语〉
〈主语〉::=〈代词〉|〈名词〉
〈谓语〉::=〈动词〉
〈宾语〉::=〈代词〉|〈名词〉
〈代词〉你|我|他
〈名词〉7大学生I筱霞I英语
〈动词〉::=是|学习
注:这里的符号“::=”表示“定义为”的意思,用“〈”和“〉”括住的是非终结符,没有括住的是终结符。
2. 句子
句子是语言的基本单位,是语言集中的一个元素,它由终结符构成,能由“文法”推导出。例如,上述文法可以推出“我是大学生”,所以它是句子。
3. 语法树
语法树是句子结构的一种树型表示,它代表了句子的推导结果,它有利于理解句子语法结构的层次。图 1 所示是“我是大学生”的语法树。
有了以上基础知识,现在来介绍解释器模式的结构就简单了。解释器模式的结构与组合模式相似,不过其包含的组成元素比组合模式多,而且组合模式是对象结构型模式,而解释器模式是类行为型模式。
模式的结构
解释器模式包含以下主要角色。
-
抽象表达式( Abstract Expression
)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。 -
终结符表达式( Terminal Expression
)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。 -
非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。 -
环境( Context
)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。 -
客户端( Client
):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
解释器模式的结构图如图
模式的实现
1. 抽象表达式类(AbstractExpression)
public interface AbstractExpression {
void interpret(String info); //解释方法
}
2. 终结符表达式类(TerminalExpression)
public class TerminalExpression implements AbstractExpression {
@Override
public void interpret(String info) {
//对终结符表达式的处理
}
}
3. 非终结符表达式类(NonterminalExpression)
public class NonterminalExpression {
private AbstractExpression exp1;
private AbstractExpression exp2;
public void interpret(String info) {
//非对终结符表达式的处理
}
}
4. 环境类(Context)
public class Context {
private AbstractExpression exp;
public Context() {
//数据初始化
}
public void operation(String info) {
//调用相关表达式类的解释方法
}
}
应用实例
用解释器模式设计一个“韶粵通”公交车卡的读卡器程序。
说明:假如“韶粵通”公交车读卡器可以判断乘客的身份,如果是“韶关”或者“广州”的“老人” “妇女”“儿童”就可以免费乘车,其他人员乘车一次扣 2 元。
分析:本实例用“解释器模式”设计比较适合,首先设计其文法规则如下。
<expression> ::= <city>的<person>
<city> ::= 韶关|广州
<person> ::= 老人|妇女|儿童
然后,根据文法规则按以下步骤设计公交车卡的读卡器程序的类图。
-
定义一个抽象表达式( Expression
)接口,它包含了解释方法interpret(String info)
。 -
定义一个终结符表达式( Terminal Expression
)类,它用集合(Set)类来保存满足条件的城市或人,并实现抽象表达式接口中的解释方法interpret(Stringinfo)
,用来判断被分析的字符串是否是集合中的终结符。 -
定义一个非终结符表达式( AndExpressicm
)类,它也是抽象表达式的子类,它包含满足条件的城市的终结符表达式对象和满足条件的人员的终结符表达式对象,并实现interpret(String info)
方法,用来判断被分析的字符串是否是满足条件的城市中的满足条件的人员。 -
最后,定义一个环境( Context
)类,它包含解释器需要的数据,完成对终结符表达式的初始化,并定义一个方法freeRide(String info)
调用表达式对象的解释方法来对被分析的字符串进行解释。其结构图如图
代码实现
1. 抽象表达式类(Expression)
public interface Expression {
boolean interpret(String info);
}
2. 终结符表达式类(TerminalExpression)
public class TerminalExpression implements Expression {
public TerminalExpression(String[] data) {
for (int i = 0; i < data.length; i++) set.add(data[i]);
}
private Set<String> set = new HashSet<String>();
@Override
public boolean interpret(String info) {
if (set.contains(info)) {
return true;
}
return false;
}
}
3. 非终结符表达式类(AndExpression)
public class AndExpression implements Expression {
private Expression city = null;
private Expression person = null;
public AndExpression(Expression city,Expression person){
this.city = city;
this.person = person;
}
@Override
public boolean interpret(String info) {
String s[] = info.split("的");
return city.interpret(s[0]) && person.interpret(s[1]);
}
}
4. 环境类(Context)
public class Context {
private String[] citys = {"韶关", "广州"};
private String[] persons = {"老人", "妇女", "儿童"};
private Expression cityPerson;
public Context() {
Expression city = new TerminalExpression(citys);
Expression person = new TerminalExpression(persons);
cityPerson = new AndExpression(city, person);
}
public void freeRide(String info) {
boolean ok = cityPerson.interpret(info);
if (ok) System.out.println("您是" + info + ",您本次乘车免费!");
else System.out.println(info + ",您不是免费人员,本次乘车扣费2元!");
}
}
5. 解释器模式测试类(InterpreterPatternTest)
public class InterpreterPatternTest {
public static void main(String[] args) {
Context bus = new Context();
bus.freeRide("韶关的老人");
bus.freeRide("韶关的年轻人");
bus.freeRide("广州的妇女");
bus.freeRide("广州的儿童");
bus.freeRide("山东的儿童");
}
}
6. 运行结果
您是韶关的老人,您本次乘车免费!
韶关的年轻人,您不是免费人员,本次乘车扣费2元!
您是广州的妇女,您本次乘车免费!
您是广州的儿童,您本次乘车免费!
山东的儿童,您不是免费人员,本次乘车扣费2元!
小结
注意:解释器模式在实际的软件开发中使用比较少,因为它会引起效率、性能以及维护等问题。如果碰到对表达式的解释,在
Java
中可以用Expression4J
或Jep
等来设计。
在项目开发中,如果要对数据表达式进行分析与计算,无须再用解释器模式进行设计了,Java 提供了以下强大的数学公式解析器:Expression4J、MESP(Math Expression String Parser) 和 Jep 等,它们可以解释一些复杂的文法,功能强大,使用简单。
现在以 Jep 为例来介绍该工具包的使用方法。Jep 是 Java expression parser 的简称,即 Java 表达式分析器,它是一个用来转换和计算数学表达式的 Java 库。通过这个程序库,用户可以以字符串的形式输入一个任意的公式,然后快速地计算出其结果。而且 Jep 支持用户自定义变量、常量和函数,它包括许多常用的数学函数和常量。
总结
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,它涉及算法与对象间职责的分配。
到现在为止23种设计模式,已经全部介绍完了,后面会借助一些项目或者案例详细说下应用场景。
忙时做业绩,闲时修内功。我是janker,下期见~
原文始发于微信公众号(爪哇干货分享):设计模式第六弹 – 行为型模式 [二]
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/171833.html