例如“解决 bug” 程序:
//新建一个程序员抽象类:
public abstract class Programmer {
protected Programmer next;
public void setNext(Programmer next) {
this.next = next;
}
abstract void handle(Bug bug);
}
在这个抽象类中:
next 对象表示如果自己解决不了,需要将责任传递给的下一个人;
handle 方法表示自己处理此 bug 的逻辑,在这里判断是自己解决或者继续传递。 新建菜鸟程序员类:
public class NewbieProgrammer extends Programmer {
@Override
public void handle(Bug bug) {
if (bug.value > 0 && bug.value <= 20) {
solve(bug);
} else if (next != null) {
next.handle(bug);
}
}
private void solve(Bug bug) {
System.out.println("菜鸟程序员解决了一个难度为 " + bug.value + " 的 bug");
}
}
新建普通程序员类:
public class NormalProgrammer extends Programmer {
@Override
public void handle(Bug bug) {
if (bug.value > 20 && bug.value <= 50) {
solve(bug);
} else if (next != null) {
next.handle(bug);
}
}
private void solve(Bug bug) {
System.out.println("普通程序员解决了一个难度为 " + bug.value + " 的 bug");
}
}
新建优秀程序员类:
public class GoodProgrammer extends Programmer {
@Override
public void handle(Bug bug) {
if (bug.value > 50 && bug.value <= 100) {
solve(bug);
} else if (next != null) {
next.handle(bug);
}
}
private void solve(Bug bug) {
System.out.println("优秀程序员解决了一个难度为 " + bug.value + " 的 bug");
}
}
客户端测试:
import org.junit.Test;
public class Client4 {
@Test
public void test() {
NewbieProgrammer newbie = new NewbieProgrammer();
NormalProgrammer normal = new NormalProgrammer();
GoodProgrammer good = new GoodProgrammer();
Bug easy = new Bug(20);
Bug middle = new Bug(50);
Bug hard = new Bug(100);
// 组成责任链
newbie.setNext(normal);
normal.setNext(good);
// 从菜鸟程序员开始,沿着责任链传递
newbie.handle(easy);
newbie.handle(middle);
newbie.handle(hard);
}
}
在客户端中,我们通过 setNext() 方法将三个程序员组成了一条责任链,由菜鸟程序员接收所有的 bug,发现自己不能处理的 bug,就传递给普通程序员,普通程序员收到 bug 后,如果发现自己不能解决,则传递给优秀程序员。
责任链思想在生活中有很多应用,比如假期审批、加薪申请等,在员工提出申请后,从经理开始,由你的经理决定自己处理或是交由更上一层的经理处理。 再比如处理客户投诉时,从基层的客服人员开始,决定自己回应或是上报给领导,领导再判断是否继续上报。
理清了责任链模式,笔者突然回想起,公司的测试组每次提出 bug 后,总是先指派给我!一瞬间仿佛明白了什么了不得的道理,不禁流下了没技术的眼泪。
(哈哈)
优点
1.降低了对象之间的耦合度。在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,所以责任链将请求的发送者和请求的处理者解耦了。
2.扩展性强,满足开闭原则。可以根据需要增加新的请求处理类。
3.灵活性强。可以动态地改变链内的成员或者改变链的次序来适应流程的变化。
4.简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的条件判断语句。
5.责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。不再需要 “项目经理” 来处理所有的责任分配任务。
5.但我们在使用中也发现了它的一个明显缺点,如果这个 bug 没人处理,可能导致 “程序员祭天” 异常。其主要缺点有:
6.不能保证每个请求一定被处理,该请求可能一直传到链的末端都得不到处理。
7.如果责任链过长,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
8.责任链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于责任链拼接次序错误而导致系统出错,比如可能出现循环调用。
将一些操作(命令)类实现命令模式的接口,就可以统一执行这些操作(命令)。
命令模式的通用接口:
/**
* 功能:命令的抽象
*/
public interface ICommand {
void execute();
}
/**
* 功能:具体的命令
*/
public class ConcreteCommand implements ICommand {
Receiver receiver = new Receiver();
@Override
public void execute() {
receiver.action();
}
}
/**
* 功能:命令接收者
*/
public class Receiver {
public void action() {
System.out.println("具体执行");
}
}
/**
* 功能:命令请求者
*/
public class Invoker {
private ICommand iCommand;
public Invoker(ICommand iCommand) {
this.iCommand = iCommand;
}
public void action() {
iCommand.execute();
}
}
public class Tests {
public static void main(String[] args) {
ICommand command = new ConcreteCommand();
//传入什么类,执行什么类
Invoker invoker = new Invoker(command);
invoker.action();
}
}
宏命令就是将多个命令合并起来组成的命令。
例子:给遥控器添加一个 “睡眠” 按钮,按下时可以一键关闭大门,关闭电灯,关闭电视、打开音乐
public class MacroCommand implements ICommand {
// 定义一组命令
List<ICommand> commands;
public MacroCommand(List<ICommand> commands) {
this.commands = commands;
}
@Override
public void execute() {
// 宏命令执行时,每个命令依次执行
for (int i = 0; i < commands.size(); i++) {
commands.get(i).execute();
}
}
@Override
public void undo() {
// 宏命令撤销时,每个命令依次撤销
for (int i = 0; i < commands.size(); i++) {
commands.get(i).undo();
}
}
}
命令模式还可以用于请求排队。要实现请求排队功能,只需创建一个命令队列,将每个需要执行的命令依次传入队列中,然后工作线程不断地从命令队列取出队列头的命令执行即可。
事实上,安卓 app 的界面就是这么实现的。源码中使用了一个阻塞式死循环 Looper,不断地从 MessageQueue 中取出消息,交给 Handler 处理,用户的每一个操作也会通过 Handler 传递到 MessageQueue
中排队执行。
命令模式可以说将封装发挥得淋漓尽致。在我们平时的程序设计中,最常用的封装是将拥有一类职责的对象封装成类,而命令对象的唯一职责就是通过 execute 去调用一个方法,也就是说它将 “方法调用” 这个步骤封装起来了,使得我们可以对
“方法调用” 进行排队、撤销等处理。
众所周知,张三坏事做尽,是一个老法外狂徒了,所以不止一个警察会盯着张三,也就是说一个被观察者可以有多个观察者。当被观察者有事件发生时,所有观察者都能收到通知并响应。观察者模式主要处理的是一种一对多的依赖关系。
观察者模式(Observer Pattern):定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
观察者的接口:
public interface Observer {
void update(String event);
}
接口中只有一个 update 方法,用于对被观察者发出的事件做出响应。
被观察者的父类:
public class Observable {
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
public void notifyObservers(String event) {
for (Observer observer : observers) {
observer.update(event);
}
}
}
被观察者中维护了一个观察者列表,提供了三个方法:
addObserver:将 observer 对象添加到观察者列表中 removeObserver:将 observer 对象从观察者列表中移除 notifyObservers:通知所有观察者有事件发生,具体实现是调用所有观察者的
update 方法 有了这两个基类,我们就可以定义出具体的罪犯与警察类。
警察属于观察者:
public class PoliceObserver implements Observer {
@Override
public void update(String event) {
System.out.println("警察收到消息,罪犯在" + event);
}
}
警察实现了观察者接口,当警察收到事件后,做出响应,这里的响应就是简单的打印了一条日志。
罪犯属于被观察者:
public class CriminalObservable extends Observable {
public void crime(String event) {
System.out.println("罪犯正在" + event);
notifyObservers(event);
}
}
罪犯继承自被观察者类,当罪犯有犯罪行为时,所有的观察者都会收到通知。
客户端测试:
public class Client {
@Test
public void test() {
CriminalObservable zhangSan = new CriminalObservable();
PoliceObserver police1 = new PoliceObserver();
PoliceObserver police2 = new PoliceObserver();
PoliceObserver police3 = new PoliceObserver();
zhangSan.addObserver(police1);
zhangSan.addObserver(police2);
zhangSan.addObserver(police3);
zhangSan.crime("放狗咬人");
}
}
在客户端中,我们 new 了一个张三,为其添加了三个观察者:police1,police2,police3。
运行程序,输出如下:
罪犯正在放狗咬人
警察收到消息,罪犯在放狗咬人
警察收到消息,罪犯在放狗咬人
警察收到消息,罪犯在放狗咬人
可以看到,所有的观察者都被通知到了。当某个观察者不需要继续观察时,调用 removeObserver 即可。
这就是观察者模式,它并不复杂,由于生活中一对多的关系非常常见,所以观察者模式应用广泛。
状态模式(State Pattern):当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
通俗地说,状态模式就是一个关于多态的设计模式。
如果一个对象有多种状态,并且每种状态下的行为不同,一般的做法是在这个对象的各个行为中添加 if-else 或者 switch-case
语句。但更好的做法是为每种状态创建一个状态对象,使用状态对象替换掉这些条件判断语句,使得状态控制更加灵活,扩展性也更好。
举个例子,力扣的用户有两种状态:普通用户和 PLUS 会员。PLUS 会员有非常多的专享功能,其中“模拟面试”功能非常有特色,我们便以此为例。
当普通用户点击模拟面试功能时,提示用户:模拟面试是 Plus 会员专享功能; 当 PLUS 会员点击模拟面试功能时,开始一场模拟面试。 先来看看不使用状态模式的写法,看出它的缺点后,我们再用状态模式来重构代码。
首先定义一个用户状态枚举类:
public enum State {
NORMAL, PLUS
}
NORMAL 代表普通用户状态,PLUS 代表 PLUS 会员状态。
用户的功能接口:
public interface IUser {
void mockInterview();
}
本例中我们只定义了一个模拟面试的方法,实际开发中这里可能会有许许多多的方法。
用户状态切换接口:
public interface ISwitchState {
void purchasePlus();
void expire();
}
此接口中定义了两个方法:purchasePlus 方法表示购买 Plus 会员,用户状态变为 PLUS 会员状态,expire 方法表示会员过期,用户状态变为普通用户状态。
力扣用户类:
public class User implements IUser, ISwitchState {
private State state = State.NORMAL;
@Override
public void mockInterview() {
if (state == State.PLUS) {
System.out.println("开始模拟面试");
} else {
System.out.println("模拟面试是 Plus 会员专享功能");
}
}
@Override
public void purchasePlus() {
state = State.PLUS;
}
@Override
public void expire() {
state = State.NORMAL;
}
}
用户类实现了 IUser 接口,IUser 接口中的每个功能都需要判断用户是否为 Plus 会员,也就是说每个方法中都有 if (state == State.PLUS) {} else {} 语句,如果状态不止两种,还需要用上
switch-case 语句来判断状态,这就是不使用状态模式的弊端:
判断用户状态会产生大量的分支判断语句,导致代码冗长; 当状态有增加或减少时,需要改动多个地方,违反开闭原则。
在《代码整洁之道》、《重构》两本书中都提到:应使用多态取代条件表达式。接下来我们就利用多态特性重构这份代码。为每个状态新建一个状态类,普通用户:
class Normal implements IUser {
@Override
public void mockInterview() {
System.out.println("模拟面试是 Plus 会员专享功能");
}
}
PLUS 会员:
class Plus implements IUser {
@Override
public void mockInterview() {
System.out.println("开始模拟面试");
}
}
每个状态类都实现了 IUser 接口,在接口方法中实现自己特定的行为。
用户类:
class User implements IUser, ISwitchState {
IUser state = new Normal();
@Override
public void mockInterview() {
state.mockInterview();
}
@Override
public void purchasePlus() {
state = new Plus();
}
@Override
public void expire() {
state = new Normal();
}
}
可以看到,丑陋的状态判断语句消失了,无论 IUser 接口中有多少方法,User 类都只需要调用状态类的对应方法即可。
客户端测试:
public class Client {
@Test
public void test() {
// 用户初始状态为普通用户
User user = new User();
// 输出:模拟面试是 Plus 会员专享功能
user.mockInterview();
// 用户购买 Plus 会员,状态改变
user.purchasePlus();
// 输出:开始模拟面试
user.mockInterview();
// Plus 会员过期,变成普通用户,状态改变
user.expire();
// 输出:模拟面试是 Plus 会员专享功能
user.mockInterview();
}
}
可以看到,用户状态改变后,行为也随着改变了,这就是状态模式定义的由来,它的优点是:将与特定状态相关的行为封装到一个状态对象中,使用多态代替 if-else 或者 switch-case
状态判断。缺点是必然导致类增加,这也是使用多态不可避免的缺点。
策略模式用一个成语就可以概括 —— 殊途同归。当我们做同一件事有多种方法时,就可以将每种方法封装起来,在不同的场景选择不同的策略,调用不同的方法。
策略模式(Strategy Pattern):定义了一系列算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。 我们以排序算法为例。排序算法有许多种,如冒泡排序、选择排序、插入排序,算法不同但目的相同,我们可以将其定义为不同的策略,让用户自由选择采用哪种策略完成排序。
首先定义排序算法接口:
interface ISort {
void sort(int[] arr);
}
接口中只有一个 sort 方法,传入一个整型数组进行排序,所有的排序算法都实现此接口。
冒泡排序:
class BubbleSort implements ISort {
@Override
public void sort(int[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// 如果左边的数大于右边的数,则交换,保证右边的数字最大
arr[j + 1] = arr[j + 1] + arr[j];
arr[j] = arr[j + 1] - arr[j];
arr[j + 1] = arr[j + 1] - arr[j];
}
}
}
}
}
选择排序:
class SelectionSort implements ISort {
@Override
public void sort(int[] arr) {
int minIndex;
for (int i = 0; i < arr.length - 1; i++) {
minIndex = i;
for (int j = i + 1; j < arr.length; j++) {
if (arr[minIndex] > arr[j]) {
// 记录最小值的下标
minIndex = j;
}
}
// 将最小元素交换至首位
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
}
插入排序:
class InsertSort implements ISort {
@Override
public void sort(int[] arr) {
// 从第二个数开始,往前插入数字
for (int i = 1; i < arr.length; i++) {
int currentNumber = arr[i];
int j = i - 1;
// 寻找插入位置的过程中,不断地将比 currentNumber 大的数字向后挪
while (j >= 0 && currentNumber < arr[j]) {
arr[j + 1] = arr[j];
j--;
}
// 两种情况会跳出循环:1. 遇到一个小于或等于 currentNumber 的数字,跳出循环,currentNumber 就坐到它后面。
// 2. 已经走到数列头部,仍然没有遇到小于或等于 currentNumber 的数字,也会跳出循环,此时 j 等于 -1,currentNumber 就坐到数列头部。
arr[j + 1] = currentNumber;
}
}
}
接下来我们需要创建一个环境类,将每种算法都作为一种策略封装起来,客户端将通过此环境类选择不同的算法完成排序。
class Sort implements ISort {
private ISort sort;
Sort(ISort sort) {
this.sort = sort;
}
@Override
public void sort(int[] arr) {
sort.sort(arr);
}
// 客户端通过此方法设置不同的策略
public void setSort(ISort sort) {
this.sort = sort;
}
}
在此类中,我们保存了一个 ISort 接口的实现对象,在构造方法中,将其初始值传递进来,排序时调用此对象的 sort 方法即可完成排序。
我们也可以为 ISort 对象设定一个默认值,客户端如果没有特殊需求,直接使用默认的排序策略即可。
setSort 方法就是用来选择不同的排序策略的,客户端调用如下:
public class Client {
@Test
public void test() {
int[] arr = new int[]{6, 1, 2, 3, 5, 4};
Sort sort = new Sort(new BubbleSort());
// 这里可以选择不同的策略完成排序
// sort.setSort(new InsertSort());
// sort.setSort(new SelectionSort());
sort.sort(arr);
// 输出 [1, 2, 3, 4, 5, 6]
System.out.println(Arrays.toString(arr));
}
}
这就是基本的策略模式,通过策略模式我们可以为同一个需求选择不同的算法,以应付不同的场景。比如我们知道冒泡排序和插入排序是稳定的,而选择排序是不稳定的,当我们需要保证排序的稳定性就可以采用冒泡排序和插入排序,不需要保证排序的稳定性时可以采用选择排序。
策略模式还可以应用在图片缓存中,当我们开发一个图片缓存框架时,可以通过提供不同的策略类,让用户根据需要选择缓存解码后的图片、缓存未经解码的数据或者不缓存任何内容。在一些开源的图片加载框架中,就采用了这种设计。
策略模式扩展性和灵活性都相当不错。当有新的策略时,只需要增加一个策略类;要修改某个策略时,只需要更改具体的策略类,其他地方的代码都无需做任何调整。
这样策略模式还有一个弊端,如本系列第一篇文章中的工厂模式所言:每 new 一个对象,相当于调用者多知道了一个类,增加了类与类之间的联系,不利于程序的松耦合。
所以使用策略模式时,更好的做法是与工厂模式结合,将不同的策略对象封装到工厂类中,用户只需要传递不同的策略类型,然后从工厂中拿到对应的策略对象即可。接下来我们就来一起实现这种工厂模式与策略模式结合的混合模式。
创建排序策略枚举类:
enum SortStrategy {BUBBLE_SORT, SELECTION_SORT, INSERT_SORT}
在 Sort 类中使用简单工厂模式:
class Sort implements ISort {
private ISort sort;
Sort(SortStrategy strategy) {
setStrategy(strategy);
}
@Override
public void sort(int[] arr) {
sort.sort(arr);
}
// 客户端通过此方法设置不同的策略
public void setStrategy(SortStrategy strategy) {
switch (strategy) {
case BUBBLE_SORT:
sort = new BubbleSort();
break;
case SELECTION_SORT:
sort = new SelectionSort();
break;
case INSERT_SORT:
sort = new InsertSort();
break;
default:
throw new IllegalArgumentException("There's no such strategy yet.");
}
}
}
利用简单工厂模式,我们将创建策略类的职责移到了 Sort 类中。如此一来,客户端只需要和 Sort 类打交道,通过 SortStrategy 选择不同的排序策略即可。
客户端:
public class Client {
@Test
public void test() {
int[] arr = new int[]{6, 1, 2, 3, 5, 4};
Sort sort = new Sort(SortStrategy.BUBBLE_SORT); // 可以通过选择不同的策略完成排序
// sort.setStrategy(SortStrategy.SELECTION_SORT);
// sort.setStrategy(SortStrategy.INSERT_SORT);
sort.sort(arr); // 输出 [1, 2, 3, 4, 5, 6]
System.out.println(Arrays.toString(arr));
}
}
通过简单工厂模式与策略模式的结合,我们最大化地减轻了客户端的压力。这是我们第一次用到混合模式,但实际开发中会遇到非常多的混合模式,学习设计模式的过程只能帮助我们各个击破,真正融会贯通还需要在实际开发中多加操练。
需要注意的是,策略模式与状态模式非常类似,甚至他们的 UML
类图都是一模一样的。两者都是采用一个变量来控制程序的行为。策略模式通过不同的策略执行不同的行为,状态模式通过不同的状态值执行不同的行为。两者的代码很类似,他们的区别主要在于程序的目的不同。
使用策略模式时,程序只需选择一种策略就可以完成某件事。也就是说每个策略类都是完整的,都能独立完成这件事情,如上文所言,强调的是殊途同归。
使用状态模式时,程序需要在不同的状态下不断切换才能完成某件事,每个状态类只能完成这件事的一部分,需要所有的状态类组合起来才能完整的完成这件事,强调的是随势而动。
模板方法模式(Template Method Pattern):定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
通俗地说,模板方法模式就是一个关于继承的设计模式。
每一个被继承的父类都可以认为是一个模板,它的某些步骤是稳定的,某些步骤被延迟到子类中实现。
这和我们平时生活中使用的模板也是一样的。比如我们请假时,通常会给我们一份请假条模板,内容是已经写好的,只需要填写自己的姓名和日期即可。
本人 ___ 因 ___ 需请假 ___ 天,望批准! 这个模板用代码表示如下:
abstract class LeaveRequest {
void request() {
System.out.print("本人");
System.out.print(name());
System.out.print("因");
System.out.print(reason());
System.out.print("需请假");
System.out.print(duration());
System.out.print("天,望批准");
}
abstract String name();
abstract String reason();
abstract String duration();
}
在这份模板中,所有的其他步骤(固定字符串)都是稳定的,只有姓名、请假原因、请假时长是抽象的,需要延迟到子类去实现。
继承此模板,实现具体步骤的子类:
class MyLeaveRequest extends LeaveRequest {
@Override
String name() {
return "阿笠";
}
@Override
String reason() {
return "参加力扣周赛";
}
@Override
String duration() {
return "0.5";
}
}
测试:
本人阿笠因参加力扣周赛需请假0.5天,望批准
new MyLeaveRequest().request(); 在使用模板方法模式时,我们可以为不同的模板方法设置不同的控制权限:
如果不希望子类覆写模板中的某个方法,使用 final 修饰此方法; 如果要求子类必须覆写模板中的某个方法,使用 abstract 修饰此方法; 如果没有特殊要求,可使用 protected 或 public
修饰此方法,子类可根据实际情况考虑是否覆写。
以我们去吃自助餐为例,每个人喜欢的食物是不一样的,比如 Aurora 喜欢吃龙虾和西瓜,Kevin
喜欢吃牛排和香蕉,餐厅不可能单独为某一位顾客专门准备食物。所以餐厅的做法是将所有的食物都准备好,顾客按照需求自由取用。此时,顾客和餐厅之间就形成了一种访问者与被访问者的关系。
准备好各种食物的餐厅:
class Restaurant {
private String lobster = "lobster";
private String watermelon = "watermelon";
private String steak = "steak";
private String banana = "banana";
}
在餐厅类中,我们提供了四种食物:龙虾、西瓜、牛排、香蕉。
为顾客提供的接口:
public interface IVisitor {
void chooseLobster(String lobster);
void chooseWatermelon(String watermelon);
void chooseSteak(String steak);
void chooseBanana(String banana);
}
接口中提供了四个方法, 让顾客依次选择每种食物。
在餐厅中提供接收访问者的方法:
class Restaurant {
//...
public void welcome(IVisitor visitor) {
visitor.chooseLobster(lobster);
visitor.chooseWatermelon(watermelon);
visitor.chooseSteak(steak);
visitor.chooseBanana(banana);
}
}
在 welcome 方法中,我们将食物依次传递给访问者对应的访问方法。这时候,顾客如果想要访问餐厅选择自己喜欢的食物,只需要实现 IVisitor 接口即可。
比如顾客 Aurora 类:
public class Aurora implements IVisitor {
@Override
public void chooseLobster(String lobster) {
System.out.println("Aurora gets a " + lobster);
}
@Override
public void chooseWatermelon(String watermelon) {
System.out.println("Aurora gets a " + watermelon);
}
@Override
public void chooseSteak(String steak) {
System.out.println("Aurora doesn't like " + steak);
}
@Override
public void chooseBanana(String banana) {
System.out.println("Aurora doesn't like " + banana);
}
}
在此类中,顾客根据自己的喜好依次选择每种食物。
客户端测试:
public class Client {
@Test
public void test() {
Restaurant restaurant = new Restaurant();
IVisitor Aurora = new Aurora();
restaurant.welcome(Aurora);
}
}
运行程序,输出如下:
Aurora gets a lobster Aurora gets a watermelon Aurora doesn’t like steak Aurora doesn’t like banana
可以看到,Aurora 对每一种食物做出了自己的选择,这就是一个最简单的访问者模式,它已经体现出了访问者模式的核心思想:将数据的结构和对数据的操作分离。
访问者模式(Visitor Pattern):表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
System.out.println("Aurora gets a " + watermelon);
}
@Override
public void chooseSteak(String steak) {
System.out.println("Aurora doesn't like " + steak);
}
@Override
public void chooseBanana(String banana) {
System.out.println("Aurora doesn't like " + banana);
}
}
在此类中,顾客根据自己的喜好依次选择每种食物。
客户端测试:
````java
public class Client {
@Test
public void test() {
Restaurant restaurant = new Restaurant();
IVisitor Aurora = new Aurora();
restaurant.welcome(Aurora);
}
}
运行程序,输出如下:
Aurora gets a lobster Aurora gets a watermelon Aurora doesn’t like steak Aurora doesn’t like banana
可以看到,Aurora 对每一种食物做出了自己的选择,这就是一个最简单的访问者模式,它已经体现出了访问者模式的核心思想:将数据的结构和对数据的操作分离。
访问者模式(Visitor Pattern):表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
本例中,顾客需要选择餐厅的食物,由于每个顾客对食物的选择是不一样的,如果在餐厅类中处理每位顾客的需求,必然导致餐厅类职责过多。所以我们并没有在餐厅类中处理顾客的需求,而是将所有的食物通过接口暴露出去,欢迎每位顾客来访问。顾客只要实现访问者接口就能访问到所有的食物,然后在接口方法中做出自己的选择。