Java开发的23种设计模式详解(行为型模式)
相关文章链接:
观前提示:
本文所使用Eclipse版本为Photon Release (4.8.0),Idea版本为ultimate 2019.1,JDK版本为1.8.0_141。
1.访问者模式
1.1 定义
表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。
1.2 组成
-
Visitor 抽象访问者角色,为该对象结构中具体元素角色声明一个访问操作接口。该操作接口的名字和参数标识了发送访问请求给具体访问者的具体元素角色,这样访问者就可以通过该元素角色的特定接口直接访问它。
-
ConcreteVisitor.具体访问者角色,实现Visitor声明的接口。
-
Element 定义一个接受访问操作(accept()),它以一个访问者(Visitor)作为参数。
-
ConcreteElement 具体元素,实现了抽象元素(Element)所定义的接受操作接口。
-
ObjectStructure 结构对象角色,这是使用访问者模式必备的角色。它具备以下特性:能枚举它的元素;可以提供一个高层接口以允许访问者访问它的元素;如有需要,可以设计成一个复合对象或者一个聚集(如一个列表或无序集合)。
1.3 例子
我们以看房为例
目录结构如下
抽象访问者Visitor.java
package testDesignPatterns.testBehavioralModel.testVisitorPattern;
public interface Visitor {
void visit(OneFamilyHouse oneFamilyHouse);
void visit(TwoFamilyHouse twoFamilyHouse);
void visit(ThreeFamilyHouse threeFamilyHouse);
}
具体访问者ConcreteVisitor.java
package testDesignPatterns.testBehavioralModel.testVisitorPattern;
public class ConcreteVisitor implements Visitor {
@Override
public void visit(OneFamilyHouse oneFamilyHouse) {
System.out.println("这是一户型房子");
}
@Override
public void visit(TwoFamilyHouse twoFamilyHouse) {
System.out.println("这是两户型房子");
}
@Override
public void visit(ThreeFamilyHouse threeFamilyHouse) {
System.out.println("这是三户型房子");
}
}
抽象元素House.java
package testDesignPatterns.testBehavioralModel.testVisitorPattern;
public interface House {
void show(Visitor visitor);
}
具体元素 OneFamilyHouse.java,TwoFamilyHouse.java,ThreeFamilyHouse.java
package testDesignPatterns.testBehavioralModel.testVisitorPattern;
public class OneFamilyHouse implements House {
@Override
public void show(Visitor visitor) {
visitor.visit(this);
}
}
package testDesignPatterns.testBehavioralModel.testVisitorPattern;
public class TwoFamilyHouse implements House {
@Override
public void show(Visitor visitor) {
visitor.visit(this);
}
}
package testDesignPatterns.testBehavioralModel.testVisitorPattern;
public class ThreeFamilyHouse implements House {
@Override
public void show(Visitor visitor) {
visitor.visit(this);
}
}
测试类Test.java
package testDesignPatterns.testBehavioralModel.testVisitorPattern;
public class Test {
public static void main(String[] args) {
ConcreteVisitor concreteVisitor = new ConcreteVisitor();
OneFamilyHouse oneFamilyHouse = new OneFamilyHouse();
TwoFamilyHouse twoFamilyHouse = new TwoFamilyHouse();
ThreeFamilyHouse threeFamilyHouse = new ThreeFamilyHouse();
oneFamilyHouse.show(concreteVisitor);
twoFamilyHouse.show(concreteVisitor);
threeFamilyHouse.show(concreteVisitor);
}
}
运行结果如下
1.4 适用场景
-
一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
-
需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor模式使得你可以将相关的操作集中起来 定义在一个类中。
-
当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作。
-
定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。
1.5 优缺点
优点
-
符合单一职责原则:凡是适用访问者模式的场景中,元素类中需要封装在访问者中的操作必定是与元素类本身关系不大且是易变的操作,使用访问者模式一方面符合单一职责原则,另一方面,因为被封装的操作通常来说都是易变的,所以当发生变化时,就可以在不改变元素类本身的前提下,实现对变化部分的扩展。
-
扩展性良好:元素类可以通过接受不同的访问者来实现对不同操作的扩展。
缺点
-
具体元素对访问者公布细节,违反了迪米特原则。
-
具体元素变更比较困难。
-
违反了依赖倒置原则,依赖了具体类,没有依赖抽象。
2.模板模式
2.1 定义
模板模式定义了一个算法的步骤,并允许子类别为一个或多个步骤提供其实践方式。让子类别在不改变算法架构的情况下,重新定义算法中的某些步骤。
2.2 例子
我们以动物为例
目录结构如下
创建一个抽象类Animal.java,它的模板方法被设置为 final。
package testDesignPatterns.testBehavioralModel.testTemplate;
public abstract class Animal {
abstract void eat();
abstract void drink();
abstract void sleep();
//模板
public final void workAndRest(){
eat();
drink();
sleep();
}
}
Dog.java
package testDesignPatterns.testBehavioralModel.testTemplate;
public class Dog extends Animal {
@Override
void eat() {
System.out.println("Dog eat");
}
@Override
void drink() {
System.out.println("Dog drink");
}
@Override
void sleep() {
System.out.println("Dog sleep");
}
}
Cat.java
package testDesignPatterns.testBehavioralModel.testTemplate;
public class Cat extends Animal {
@Override
void eat() {
System.out.println("Cat eat");
}
@Override
void drink() {
System.out.println("Cat drink");
}
@Override
void sleep() {
System.out.println("Cat sleep");
}
}
适用Animal模板定义动物作息方式Test.java
package testDesignPatterns.testBehavioralModel.testTemplate;
public class Test {
public static void main(String[] args) {
Animal animal = new Dog();
animal.workAndRest();
System.out.println("---------------------------------");
animal = new Cat();
animal.workAndRest();
}
}
运行结果如下
2.3 适用场景
-
有多个子类共有的方法,且逻辑相同。
-
重要的、复杂的方法,可以考虑作为模板方法。
注:为防止恶意操作,一般模板方法都加上 final 关键词。
2.4 优缺点
优点
-
封装不变部分,扩展可变部分。
-
提取公共代码,便于维护。
-
行为由父类控制,子类实现。
缺点
- 每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
3.策略模式
3.1 定义
策略模式作为一种软件设计模式,指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。
3.2 组成
-
抽象策略角色: 策略类,通常由一个接口或者抽象类实现。
-
具体策略角色:包装了相关的算法和行为。
-
环境角色:持有一个策略类的引用,最终给客户端调用。
3.3 例子
我们以两数的加减为例
目录结构如下
抽象策略角色Strategy.java
package testDesignPatterns.testBehavioralModel.testStrategy;
public interface Strategy {
public int doOperation(int n1, int n2);
}
具体策略角色Add.java,Subtract.java
package testDesignPatterns.testBehavioralModel.testStrategy;
public class Add implements Strategy {
@Override
public int doOperation(int n1, int n2) {
return n1 + n2;
}
}
package testDesignPatterns.testBehavioralModel.testStrategy;
public class Subtract implements Strategy {
@Override
public int doOperation(int n1, int n2) {
return n1 - n2;
}
}
环境角色Context.java
package testDesignPatterns.testBehavioralModel.testStrategy;
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public int executeStrategy(int n1, int n2) {
return strategy.doOperation(n1, n2);
}
}
测试类Test.java
package testDesignPatterns.testBehavioralModel.testStrategy;
public class Test {
public static void main(String[] args) {
Context context = new Context(new Add());
System.out.println("3 + 2 = " + context.executeStrategy(3, 2 ));
context = new Context(new Subtract());
System.out.println("3 - 2 = " + context.executeStrategy(3, 2 ));
}
}
运行结果如下
3.4 适用场景
-
多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。
-
需要在不同情况下使用不同的策略(算法),或者策略还可能在未来用其它方式来实现。
-
对客户隐藏具体策略(算法)的实现细节,彼此完全独立。
3.5 优缺点
优点
-
算法可以自由切换。
-
避免使用多重条件判断。
-
扩展性良好。
缺点
-
策略类会增多。
-
所有策略类都需要对外暴露。
4.状态模式
4.1 定义
当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
4.2 例子
我们以灯开关状态为例
目录结构如下
接口LightState.java
package testDesignPatterns.testState;
public interface LightState {
void state(Button button);
void print();
}
实现接口类LightOn.java,LightOff.java
package testDesignPatterns.testState;
public class LightOn implements LightState {
@Override
public void state(Button button) {
System.out.println("Light On");
button.setLightState(this);
}
@Override
public void print(){
System.out.println("Light State is On");
}
}
package testDesignPatterns.testState;
public class LightOff implements LightState {
@Override
public void state(Button button) {
System.out.println("Light Off");
button.setLightState(this);
}
@Override
public void print(){
System.out.println("Light State is Off");
}
}
开关Button.java
package testDesignPatterns.testState;
public class Button {
private LightState lightState;
public LightState getLightState() {
return lightState;
}
public void setLightState(LightState lightState) {
this.lightState = lightState;
}
}
测试类Test.java
package testDesignPatterns.testState;
public class Test {
public static void main(String[] args) {
Button button = new Button();
LightOn lightOn = new LightOn();
lightOn.state(button);
button.getLightState().print();
LightOff lightOff = new LightOff();
lightOff.state(button);
button.getLightState().print();
}
}
运行结果如下
4.3 适用场景
-
一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
-
一个操作中含有庞大的多分支结构,并且这些分支决定于对象的状态。
4.4 优缺点
优点
-
封装了转换规则。
-
枚举可能的状态,在枚举状态之前需要确定状态种类。
-
将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
-
允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
-
可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
缺点
-
状态模式的使用必然会增加系统类和对象的个数。
-
状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
-
状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。
5.观察者模式
5.1 定义
观察者模式(有时又被称为模型(Model)-视图(View)模式、源-收听者(Listener)模式或从属者模式)是软件设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。
5.2 构成
-
抽象主题(Subject)
它把所有观察者对象的引用保存到一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。 -
抽象观察者(Observer):
为所有的具体观察者定义一个接口,在得到主题通知时更新自己。 -
具体观察者(Concrete Observer):
实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题状态协调。
5.3 例子
目录结构如下
抽象主题Subject.java
package testDesignPatterns.testObserverMode;
import java.util.ArrayList;
import java.util.List;
public class Subject {
private List<Observer> observerList = new ArrayList<>();
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
notifyAllObserver();
}
public void add(Observer observer){
observerList.add(observer);
}
public void notifyAllObserver(){
for (Observer observer: observerList) {
observer.update();
}
}
}
抽象观察者Observer.java
package testDesignPatterns.testObserverMode;
public abstract class Observer {
protected Subject subject;
public abstract void update();
}
具体观察者ConcreteObserver1.java,ConcreteObserver2.java,ConcreteObserver3.java
package testDesignPatterns.testObserverMode;
public class ConcreteObserver1 extends Observer {
public ConcreteObserver1(Subject subject) {
this.subject = subject;
this.subject.add(this);
}
@Override
public void update() {
System.out.println("ConcreteObserver1 update : " + subject.getState());
}
}
package testDesignPatterns.testObserverMode;
public class ConcreteObserver2 extends Observer {
public ConcreteObserver2(Subject subject) {
this.subject = subject;
this.subject.add(this);
}
@Override
public void update() {
System.out.println("ConcreteObserver2 update : " + subject.getState());
}
}
package testDesignPatterns.testObserverMode;
public class ConcreteObserver3 extends Observer {
public ConcreteObserver3(Subject subject) {
this.subject = subject;
this.subject.add(this);
}
@Override
public void update() {
System.out.println("ConcreteObserver3 update : " + subject.getState());
}
}
测试类Test.java
package testDesignPatterns.testObserverMode;
public class Test {
public static void main(String[] args) {
Subject subject = new Subject();
new ConcreteObserver1(subject);
new ConcreteObserver2(subject);
new ConcreteObserver3(subject);
System.out.println("--------------------");
subject.setState(2);
System.out.println("--------------------");
subject.setState(9);
}
}
运行结果如下
5.4 适用场景
-
当一个抽象模型有两个方面,其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
-
当对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象需要被改变。
-
当一个对象必须通知其他对象,而它又不能假定其他对象是谁。换言之,不希望这些对象是紧密耦合的。
5.5 优缺点
优点
- 观察者模式解除了主题和具体观察者的耦合,让耦合的双方都依赖于抽象,而不是依赖具体。
缺点
- 在应用观察者模式时需要考虑一下开发小路问题,程序中包括一个被观察者和多个被观察者,开发和调试比较复杂,而且Java中的消息的通知默认是顺序执行的,一个观察者的卡顿会影响整体的执行效率。在这种情况下,一般考虑采用异步的方式。
6.备忘录模式
6.1 定义
在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
6.2 组成
-
Originator(发起人):负责创建一个备忘录Memento,用以记录当前时刻自身的内部状态,并可使用备忘录恢复内部状态。Originator可以根据需要决定Memento存储自己的哪些内部状态。
-
Memento(备忘录):负责存储Originator对象的内部状态,并可以防止Originator以外的其他对象访问备忘录。备忘录有两个接口:Caretaker只能看到备忘录的窄接口,他只能将备忘录传递给其他对象。Originator却可看到备忘录的宽接口,允许它访问返回到先前状态所需要的所有数据。
-
Caretaker(管理者):负责备忘录Memento,不能对Memento的内容进行访问或者操作。
6.3 例子
目录结构如下
创建Memento.java
package testDesignPatterns.testMementoMode;
public class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
创建Originator.java
package testDesignPatterns.testMementoMode;
public class Originator {
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public Memento saveMementoState(){
return new Memento(state);
}
public void getMementoState(Memento memento){
state = memento.getState();
}
}
创建Caretaker.java
package testDesignPatterns.testMementoMode;
import java.util.ArrayList;
import java.util.List;
public class CareTaker {
private List<Memento> mementoList = new ArrayList<>();
public void add(Memento memento){
mementoList.add(memento);
}
public Memento get(int index){
return mementoList.get(index);
}
}
创建Test.java
package testDesignPatterns.testMementoMode;
public class Test {
public static void main(String[] args) {
Originator originator = new Originator();
CareTaker careTaker = new CareTaker();
originator.setState("State1");
careTaker.add(originator.saveMementoState());
originator.setState("State2");
careTaker.add(originator.saveMementoState());
originator.setState("State3");
careTaker.add(originator.saveMementoState());
originator.setState("State4");
System.out.println("Current State: " + originator.getState());
originator.getMementoState(careTaker.get(0));
System.out.println("First saved State: " + originator.getState());
originator.getMementoState(careTaker.get(1));
System.out.println("Second saved State: " + originator.getState());
}
}
运行结果
6.4 适用场景
-
需要保存/恢复数据的相关状态场景。
-
提供一个可回滚的操作。
6.5 优缺点
优点
-
给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
-
实现了信息的封装,使得用户不需要关心状态的保存细节。
缺点
- 消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
7.中介者模式
7.1 定义
用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
7.2 组成
-
抽象中介者(mediator)
定义一个接口用于和对象通信。 -
具体中介者(concretemediator)
协调各同事对象实现协作,了解维护各个同事。 -
抽象同事角色(colleague)
规定了同事的基本类型。 -
具体同事角色(concreteColleague)
每个同事都知道中介者对象,要与同事通信则把通信告诉中介者。
7.3 例子
我们以租房为例,各房东带领住户去各个房源看房。
中介看房
目录结构如下
抽象中介者 Mediator.java
package testDesignPatterns.testBehavioralModel.testMediatorPattern;
public abstract class Mediator {
public abstract void lookHouse(String type);
}
抽象同事角色 Colleague.java
package testDesignPatterns.testBehavioralModel.testMediatorPattern;
public abstract class Colleague {
public abstract void showHouse();
}
具体中介者 HouseMediator.java
package testDesignPatterns.testBehavioralModel.testMediatorPattern;
public class HouseMediator extends Mediator {
private OneFamilyColleague oneFamilyColleague;
private TwoFamilyColleague twoFamilyColleague;
private ThreeFamilyColleague threeFamilyColleague;
public void setOneFamilyColleague(OneFamilyColleague oneFamilyColleague) {
this.oneFamilyColleague = oneFamilyColleague;
}
public void setTwoFamilyColleague(TwoFamilyColleague twoFamilyColleague) {
this.twoFamilyColleague = twoFamilyColleague;
}
public void setThreeFamilyColleague(ThreeFamilyColleague threeFamilyColleague) {
this.threeFamilyColleague = threeFamilyColleague;
}
@Override
public void lookHouse(String type){
if("一户型".equals(type)){
oneFamilyColleague.showHouse();
} else if ("两户型".equals(type)) {
twoFamilyColleague.showHouse();
} else if ("三户型".equals(type)) {
threeFamilyColleague.showHouse();
} else {
System.out.println("未找到符合的房源");
}
}
}
具体同事角色 OneFamilyColleague.java,TwoFamilyColleague.java,ThreeFamilyColleague.java
package testDesignPatterns.testBehavioralModel.testMediatorPattern;
public class OneFamilyColleague extends Colleague {
@Override
public void showHouse() {
System.out.println("这是一户型房间");
}
}
package testDesignPatterns.testBehavioralModel.testMediatorPattern;
public class TwoFamilyColleague extends Colleague{
@Override
public void showHouse() {
System.out.println("这是两户型房间");
}
}
package testDesignPatterns.testBehavioralModel.testMediatorPattern;
public class ThreeFamilyColleague extends Colleague {
@Override
public void showHouse() {
System.out.println("这是三户型房间");
}
}
测试类Test.java
package testDesignPatterns.testBehavioralModel.testMediatorPattern;
public class Test {
public static void main(String[] args) {
HouseMediator mediator = new HouseMediator();
OneFamilyColleague one = new OneFamilyColleague();
TwoFamilyColleague two = new TwoFamilyColleague();
ThreeFamilyColleague three = new ThreeFamilyColleague();
mediator.setOneFamilyColleague(one);
mediator.setTwoFamilyColleague(two);
mediator.setThreeFamilyColleague(three);
mediator.lookHouse("两户型");
mediator.lookHouse("四户型");
}
}
运行结果如下
7.4 适用场景
-
系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象。
-
想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。
7.5 优缺点
优点
-
降低了类的复杂度,将一对多转化成了一对一。
-
各个类之间的解耦。
-
符合迪米特原则。
缺点
- 中介者会庞大,变得复杂难以维护。
8.迭代器模式
8.1 定义
可以让用户透过特定的接口巡访容器中的每一个元素而不用了解底层的实现。
8.2 构成
-
Iterator(迭代器)
迭代器定义访问和遍历元素的接口。 -
ConcreteIterator (具体迭代器)
具体迭代器实现迭代器接口对该聚合遍历时跟踪当前位置。 -
Aggregate (聚合)
聚合定义创建相应迭代器对象的接口。 -
ConcreteAggregate (具体聚合)
具体聚合实现创建相应迭代器的接口,该操作返回ConcreteIterator的一个适当的实例。
8.3 例子
目录结构如下
迭代器 Iterator.java
package testDesignPatterns.testBehavioralModel.testIteratorPattern;
public interface Iterator {
void next();
Object getCurrent();
boolean hasNext();
}
聚合 Aggregate.java
package testDesignPatterns.testBehavioralModel.testIteratorPattern;
public interface Aggregate {
Iterator getIterator();
}
具体聚合 ConcreteAggregate.java
package testDesignPatterns.testBehavioralModel.testIteratorPattern;
import java.util.ArrayList;
import java.util.List;
public class ConcreteAggregate implements Aggregate {
private List<String> list = new ArrayList<>();
public void add(String str){
list.add(str);
}
@Override
public Iterator getIterator() {
return new ConcreteIterator();
}
private class ConcreteIterator implements Iterator {
private int index;
@Override
public void next() {
if(index < list.size()){
index++;
}
}
@Override
public Object getCurrent() {
return list.get(index);
}
@Override
public boolean hasNext() {
if(index < list.size()){
return true;
}
return false;
}
}
}
测试 Test.java
package testDesignPatterns.testBehavioralModel.testIteratorPattern;
public class Test {
public static void main(String[] args) {
ConcreteAggregate ca = new ConcreteAggregate();
ca.add("第一个元素");
ca.add("第二个元素");
ca.add("第三个元素");
ca.add("第四个元素");
Iterator iterator = ca.getIterator();
while (iterator.hasNext()){
System.out.println(iterator.getCurrent());
iterator.next();
}
}
}
运行结果如下
8.4 适用场景
-
访问一个聚合对象的内容而无需暴露它的内部表示。
-
支持对聚合对象的多种遍历。
-
为遍历不同的聚合结构提供一个统一的接口。
8.5 优缺点
优点
-
它支持以不同的方式遍历一个聚合对象。
-
迭代器简化了聚合类。
-
在同一个聚合上可以有多个遍历。
-
在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
缺点
- 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
9.解释器模式
9.1 定义
实现了一个表达式接口,该接口解释一个特定的上下文。
9.2 组成
-
抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
-
终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
-
非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
-
环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
-
客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
9.2 例子
目录结构如下
接口Expression.java
package testDesignPatterns.testBehavioralModel.testInterpreterPattern;
public interface Expression {
boolean interpret(String info);
}
终结符表达式TerminalExpression.java
package testDesignPatterns.testBehavioralModel.testInterpreterPattern;
import java.util.HashSet;
import java.util.Set;
public class TerminalExpression implements Expression {
private Set<String> set = new HashSet<>();
public TerminalExpression(String[] infos) {
for (String str: infos){
set.add(str);
}
}
@Override
public boolean interpret(String info) {
if(set.contains(info)){
return true;
}
return false;
}
}
非终结符表达式AndExpression.java
package testDesignPatterns.testBehavioralModel.testInterpreterPattern;
public class AndExpression implements Expression{
private Expression exp1;
private Expression exp2;
public AndExpression(Expression exp1, Expression exp2) {
this.exp1 = exp1;
this.exp2 = exp2;
}
@Override
public boolean interpret(String info) {
String[] array = info.split("会");
return exp1.interpret(array[0]) && exp2.interpret(array[1]);
}
}
环境类AndContext.java
package testDesignPatterns.testBehavioralModel.testInterpreterPattern;
public class AndContext {
private String[] animalArray = {"鸡", "鸭", "猫", "狗"};
private String[] callArray = {"进食", "睡觉"};
private Expression expression = null;
public AndContext() {
Expression exp1 = new TerminalExpression(animalArray);
Expression exp2 = new TerminalExpression(callArray);
expression = new AndExpression(exp1, exp2);
}
public boolean judge(String info){
return expression.interpret(info);
}
}
测试类Test.java
package testDesignPatterns.testBehavioralModel.testInterpreterPattern;
public class Test {
public static void main(String[] args) {
AndContext andContext = new AndContext();
System.out.println("鸡会进食 : " + andContext.judge("鸡会进食"));
System.out.println("猫会飞 : " + andContext.judge("猫会飞"));
}
}
运行结果如下
9.3 适用场景
-
可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
-
一些重复出现的问题可以用一种简单的语言来进行表达。
-
一个简单语法需要解释的场景。
9.4 优缺点
优点
-
可扩展性比较好,灵活。
-
增加了新的解释表达式的方式。
-
易于实现简单文法。
缺点
-
可利用场景比较少。
-
对于复杂的文法比较难维护。
-
解释器模式会引起类膨胀。
-
解释器模式采用递归调用方法。
10.命令模式
10.1 定义
请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
10.2 组成
-
Command:定义命令的接口,声明执行的方法。
-
ConcreteCommand:命令接口实现对象,是“虚”的实现;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
-
Receiver:接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
-
Invoker:要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
-
Client:创建具体的命令对象,并且设置命令对象的接收者。注意这个不是我们常规意义上的客户端,而是在组装命令对象和接收者,或许,把这个Client称为装配者会更好理解,因为真正使用命令的客户端是从Invoker来触发执行。
10.3 例子
我们以计算器为例
目录结构如下
通用接口Order.java
package testDesignPatterns.testBehavioralModel.testCommandPattern;
public interface Order {
void calculate();
}
实现类Add.java,Subtract.java,Multiply.java
package testDesignPatterns.testBehavioralModel.testCommandPattern;
public class Add implements Order {
private OrderRequester orderRequester;
public Add(OrderRequester orderRequester) {
this.orderRequester = orderRequester;
}
@Override
public void calculate() {
orderRequester.operateAdd();
}
}
package testDesignPatterns.testBehavioralModel.testCommandPattern;
public class Subtract implements Order {
private OrderRequester orderRequester;
public Subtract(OrderRequester orderRequester) {
this.orderRequester = orderRequester;
}
@Override
public void calculate() {
orderRequester.operateSubtract();
}
}
package testDesignPatterns.testBehavioralModel.testCommandPattern;
public class Multiply implements Order {
private OrderRequester orderRequester;
public Multiply(OrderRequester orderRequester) {
this.orderRequester = orderRequester;
}
@Override
public void calculate() {
orderRequester.operateMultiply();
}
}
命令请求者OrderRequester.java
package testDesignPatterns.testBehavioralModel.testCommandPattern;
public class OrderRequester {
private int num1 = 10;
private int num2 = 4;
public void operateAdd(){
System.out.println(num1 + " + " + num2 + " = " + (num1 + num2));
}
public void operateSubtract(){
System.out.println(num1 + " - " + num2 + " = " + (num1 - num2));
}
public void operateMultiply(){
System.out.println(num1 + " * " + num2 + " = " + (num1 * num2));
}
}
命令执行者OrderExecutor.java
package testDesignPatterns.testBehavioralModel.testCommandPattern;
import java.util.ArrayList;
import java.util.List;
public class OrderExecutor {
private List<Order> orderList = new ArrayList<>();
public void addOrder(Order order){
orderList.add(order);
}
public void excuteOrder(){
for (Order order: orderList){
order.calculate();
}
orderList.clear();
}
}
测试类Test.java
package testDesignPatterns.testBehavioralModel.testCommandPattern;
import org.springframework.web.multipart.MultipartFile;
public class Test {
public static void main(String[] args) {
OrderRequester orderRequester = new OrderRequester();
Add add = new Add(orderRequester);
Subtract subtract = new Subtract(orderRequester);
Multiply multiply = new Multiply(orderRequester);
OrderExecutor orderExecutor = new OrderExecutor();
orderExecutor.addOrder(add);
orderExecutor.addOrder(subtract);
orderExecutor.addOrder(multiply);
orderExecutor.excuteOrder();
}
}
运行结果如下
10.4 适用场景
-
系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
-
系统需要在不同的时间指定请求、将请求排队和执行请求。
-
系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
-
系统需要将一组操作组合在一起,即支持宏命令。
10.5 优缺点
优点
-
降低对象之间的耦合度。
-
新的命令可以很容易地加入到系统中。
-
可以比较容易地设计一个组合命令。
-
调用同一方法实现不同的功能。
缺点
- 使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。
11.责任链模式
11.1 定义
在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。
11.2 组成
-
抽象处理者(Handler)角色:定义出一个处理请求的接口。如果需要,接口可以定义出一个方法以设定和返回对下家的引用。这个角色通常由一个Java抽象类或者Java接口实现。
-
具体处理者(ConcreteHandler)角色:具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家。
11.3 例子
我们以请假为例,员工请假->项目经理->项目总监->部门经理。
目录结构如下
抽象处理者Leader.java
package testDesignPatterns.testBehavioralModel.testChainOfResponsibilityPattern;
public abstract class Leader {
private Leader nextLeader;
public Leader getNextLeader() {
return nextLeader;
}
public void setNextLeader(Leader nextLeader) {
this.nextLeader = nextLeader;
}
public abstract void handerLeave(int leaveDays);
}
具体处理者ProjectManager.java,ProjectDirector.java,DepartmentManager.java
package testDesignPatterns.testBehavioralModel.testChainOfResponsibilityPattern;
public class ProjectManager extends Leader {
@Override
public void handerLeave(int leaveDays) {
if(leaveDays <= 5){
System.out.println("项目经理批准请假" + leaveDays + "天");
} else {
if(getNextLeader() != null){
getNextLeader().handerLeave(leaveDays);
} else {
System.out.println("请假天数过多,无人批准!");
}
}
}
}
package testDesignPatterns.testBehavioralModel.testChainOfResponsibilityPattern;
public class ProjectDirector extends Leader {
@Override
public void handerLeave(int leaveDays) {
if(leaveDays <= 10){
System.out.println("项目总监批准请假" + leaveDays + "天");
} else {
if(getNextLeader() != null){
getNextLeader().handerLeave(leaveDays);
} else {
System.out.println("请假天数过多,无人批准!");
}
}
}
}
package testDesignPatterns.testBehavioralModel.testChainOfResponsibilityPattern;
public class DepartmentManager extends Leader {
@Override
public void handerLeave(int leaveDays) {
if(leaveDays <= 15){
System.out.println("部门经理批准请假" + leaveDays + "天");
} else {
if(getNextLeader() != null){
getNextLeader().handerLeave(leaveDays);
} else {
System.out.println("请假天数过多,无人批准!");
}
}
}
}
测试类Test.java
package testDesignPatterns.testBehavioralModel.testChainOfResponsibilityPattern;
public class Test {
public static void main(String[] args) {
Leader leader1 = new ProjectManager();
Leader leader2 = new ProjectDirector();
Leader leader3 = new DepartmentManager();
leader1.setNextLeader(leader2);
leader2.setNextLeader(leader3);
leader1.handerLeave(16);
leader1.handerLeave(11);
leader1.handerLeave(6);
leader1.handerLeave(1);
}
}
运行结果如下
11.4 适用场景
-
有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。
-
在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
-
可动态指定一组对象处理请求。
11.5 优缺点
优点
-
降低耦合度。它将请求的发送者和接收者解耦。
-
简化了对象。使得对象不需要知道链的结构。
-
增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
-
增加新的请求处理类很方便。
缺点
-
不能保证请求一定被接收。
-
系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
-
可能不容易观察运行时的特征,有碍于除错。