一.模式定义
访问者模式(Visitor Pattern):表示一个作用于某对象结构中的各元素的操作,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。访问者模式是一种对象行为型模式。
Visitor Pattern: Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.
二.模式要素
Vistor: 抽象访问者
ConcreteVisitor: 具体访问者
Element: 抽象元素
ConcreteElement: 具体元素
ObjectStructure: 对象结构
三.举例说明
1.场景带入一
假设你开了一家水果店,自己就是销售者。店里常年销售着西瓜、苹果两种水果。你每天都向顾客报出两种水果的价格。
那你可能会这样组织代码。
IFruit.java 水果接口
package example;
/**
* @program: Test
* @description: 水果接口
* @author: Lei Dong
* @create: 2019-03-03 10:27
**/
public interface IFruit {
/**
* 得到名称
*
* @return
*/
String obtainName();
/**
* 返回日常的价格
*
* @return
*/
int obtainCommonPrice();
}
Watermelon.java 西瓜
package example;
/**
* @program: Test
* @description: 西瓜
* @author: Lei Dong
* @create: 2019-03-03 10:30
**/
public class Watermelon implements IFruit {
private static final String NAME = "西瓜";
@Override
public String obtainName() {
return NAME;
}
/**
* 返回西瓜日常的价格
*
* @return
*/
@Override
public int obtainCommonPrice() {
return 2;
}
}
Apple.java 苹果
package example;
/**
* @program: Test
* @description: 苹果
* @author: Lei Dong
* @create: 2019-03-03 10:39
**/
public class Apple implements IFruit {
private static final String NAME = "苹果";
@Override
public String obtainName() {
return NAME;
}
@Override
public int obtainCommonPrice() {
return 3;
}
}
Seller.java 销售者
package example;
import java.util.List;
/**
* @program: Test
* @description: 销售者
* @author: Lei Dong
* @create: 2019-03-03 10:31
**/
class Seller {
List<IFruit> fruitList;
Seller(List<IFruit> fruitList) {
this.fruitList = fruitList;
}
/**
* 展示水果的日常价格
*/
void showCommonDetail() {
System.out.println("日常价格:");
for (IFruit fruit : fruitList) {
System.out.println(fruit.obtainName() + ":" + fruit.obtainCommonPrice() + "元/斤");
}
}
}
Main.java
package example;
import java.util.ArrayList;
import java.util.List;
/**
* @program: Test
* @description:
* @author: Lei Dong
* @create: 2019-03-03 10:27
**/
public class Main {
public static void main(String[] args) {
List<IFruit> fruitList = new ArrayList<>();
fruitList.add(new Apple());
fruitList.add(new Watermelon());
Seller seller = new Seller(fruitList);
seller.showCommonDetail();
}
}
运行结果:
你已经完美解决了现在的问题。
2.场景带入二
但是你转念一想,好像不够完善。因为这两种水果的价钱并非一成不变,逢年过节的肯定要调整价格。这个其实也不难,你会这样写代码。
IFruit.java 水果接口(增加其他节日价格的相关返回方法)
package example;
/**
* @program: Test
* @description: 水果接口
* @author: Lei Dong
* @create: 2019-03-03 10:27
**/
public interface IFruit {
/**
* 得到名称
*
* @return
*/
String obtainName();
/**
* 返回日常的价格
*
* @return
*/
int obtainCommonPrice();
/**
* 返回春节的价格
*
* @return
*/
int obtainSpringFestivalPrice();
/**
* 返回情人节的价格
*
* @return
*/
int obtainValentinesDayPrice();
}
Watermelon.java 西瓜(增加实现新的接口方法)
package example;
/**
* @program: Test
* @description: 西瓜
* @author: Lei Dong
* @create: 2019-03-03 10:30
**/
public class Watermelon implements IFruit {
private static final String NAME = "西瓜";
@Override
public String obtainName() {
return NAME;
}
@Override
public int obtainCommonPrice() {
return 2;
}
@Override
public int obtainSpringFestivalPrice() {
return 10;
}
@Override
public int obtainValentinesDayPrice() {
return 5;
}
}
Apple.java 苹果(增加实现新的接口方法)
package example;
/**
* @program: Test
* @description: 苹果
* @author: Lei Dong
* @create: 2019-03-03 10:39
**/
public class Apple implements IFruit {
private static final String NAME = "苹果";
@Override
public String obtainName() {
return NAME;
}
@Override
public int obtainCommonPrice() {
return 3;
}
@Override
public int obtainSpringFestivalPrice() {
return 12;
}
@Override
public int obtainValentinesDayPrice() {
return 30;
}
}
Seller.java 销售者(添加展示春节水果价格和情人节水果价格的方法)
package example;
import java.util.List;
/**
* @program: Test
* @description: 销售者
* @author: Lei Dong
* @create: 2019-03-03 10:31
**/
class Seller {
List<IFruit> fruitList;
Seller(List<IFruit> fruitList) {
this.fruitList = fruitList;
}
/**
* 展示水果的日常价格
*/
void showCommonDetail() {
System.out.println("日常价格:");
for (IFruit fruit : fruitList) {
System.out.println(fruit.obtainName() + ":" + fruit.obtainCommonPrice() + "元/斤");
}
}
/**
* 展示水果的春节价格
*/
void showSpringFestivalDetail() {
System.out.println("春节价格:");
for (IFruit fruit : fruitList) {
System.out.println(fruit.obtainName() + ":" + fruit.obtainSpringFestivalPrice() + "元/斤");
}
}
/**
* 展示水果的情人节价格
*/
void showValentinesDayPrice() {
System.out.println("情人节价格:");
for (IFruit fruit : fruitList) {
System.out.println(fruit.obtainName() + ":" + fruit.obtainValentinesDayPrice() + "元/斤");
}
}
}
Main.java
package example;
import java.util.ArrayList;
import java.util.List;
/**
* @program: Test
* @description:
* @author: Lei Dong
* @create: 2019-03-03 10:27
**/
public class Main {
public static void main(String[] args) {
List<IFruit> fruitList = new ArrayList<>();
fruitList.add(new Apple());
fruitList.add(new Watermelon());
Seller seller = new Seller(fruitList);
seller.showCommonDetail();
seller.showSpringFestivalDetail();
seller.showValentinesDayPrice();
}
}
运行结果:
这种实现的变化就是,每增加一个节日,就需要在水果接口IFruit中添加一个返回该节日水果价格的接口,具体的水果类再去实现新增的接口。然后需要在Seller类中新建新的方法去实现对该节日水果价格的展示。动IFruit接口和在具体的水果类中实现接口方法还能忍,但是频繁改动Seller类就比较难受了。
有没有办法说不改变Seller类也能实现这样的功能呢?有的,那就是访问者模式的写法。
访问者模式的UML图如下:
四.代码实现
IFruit.java 水果接口
package example;
/**
* @program: Test
* @description: 水果接口
* @author: Lei Dong
* @create: 2019-03-03 10:27
**/
public interface IFruit {
/**
* 得到名称
*
* @return
*/
String obtainName();
/**
* 接受访问者
*
* @param visitor
*/
void accept(IVisitor visitor);
/**
* 返回日常的价格
*
* @return
*/
int obtainCommonPrice();
/**
* 返回春节的价格
*
* @return
*/
int obtainSpringFestivalPrice();
/**
* 返回情人节的价格
*
* @return
*/
int obtainValentinesDayPrice();
}
Watermelon.java 西瓜
package example;
/**
* @program: Test
* @description: 西瓜
* @author: Lei Dong
* @create: 2019-03-03 10:30
**/
public class Watermelon implements IFruit {
private static final String NAME = "西瓜";
@Override
public String obtainName() {
return NAME;
}
@Override
public void accept(IVisitor visitor) {
visitor.visit(this);
}
@Override
public int obtainCommonPrice() {
return 2;
}
@Override
public int obtainSpringFestivalPrice() {
return 10;
}
@Override
public int obtainValentinesDayPrice() {
return 5;
}
}
Apple.java 苹果
package example;
/**
* @program: Test
* @description: 苹果
* @author: Lei Dong
* @create: 2019-03-03 10:39
**/
public class Apple implements IFruit {
private static final String NAME = "苹果";
@Override
public String obtainName() {
return NAME;
}
@Override
public void accept(IVisitor visitor) {
visitor.visit(this);
}
@Override
public int obtainCommonPrice() {
return 3;
}
@Override
public int obtainSpringFestivalPrice() {
return 12;
}
@Override
public int obtainValentinesDayPrice() {
return 30;
}
}
Seller.java 销售者
package example;
import java.util.List;
/**
* @program: Test
* @description: 销售者
* @author: Lei Dong
* @create: 2019-03-03 10:31
**/
class Seller {
List<IFruit> fruitList;
Seller(List<IFruit> fruitList) {
this.fruitList = fruitList;
}
void showPrice(IVisitor visitor) {
for (IFruit fruit : fruitList) {
fruit.accept(visitor);
}
}
}
IVisitor.java 访问者接口
package example;
/**
* @program: Test
* @description: 访问者接口
* @author: Lei Dong
* @create: 2019-03-03 11:38
**/
public interface IVisitor {
/**
* 访问西瓜
*
* @param watermelon
*/
void visit(Watermelon watermelon);
/**
* 访问苹果
*
* @param apple
*/
void visit(Apple apple);
}
CommonVisitor.java 日常访问者
package example;
/**
* @program: Test
* @description: 日常访问者
* @author: Lei Dong
* @create: 2019-03-03 11:41
**/
public class CommonVisitor implements IVisitor {
@Override
public void visit(Watermelon watermelon) {
System.out.println(watermelon.obtainName() + "的日常价格是:" + watermelon.obtainCommonPrice() + "元/斤");
}
@Override
public void visit(Apple apple) {
System.out.println(apple.obtainName() + "的日常价格是:" + apple.obtainCommonPrice() + "元/斤");
}
}
SpringFestivalVisitor.java 春节访问者
package example;
/**
* @program: Test
* @description: 春节访问者
* @author: Lei Dong
* @create: 2019-03-03 11:44
**/
public class SpringFestivalVisitor implements IVisitor {
@Override
public void visit(Watermelon watermelon) {
System.out.println(watermelon.obtainName() + "春节的价格是:" + watermelon.obtainSpringFestivalPrice() + "元/斤");
}
@Override
public void visit(Apple apple) {
System.out.println(apple.obtainName() + "春节的价格是:" + apple.obtainSpringFestivalPrice() + "元/斤");
}
}
ValentinesDayVisitor.java 情人节访问者
package example;
/**
* @program: Test
* @description: 情人节访问者
* @author: Lei Dong
* @create: 2019-03-03 11:48
**/
public class ValentinesDayVisitor implements IVisitor {
@Override
public void visit(Watermelon watermelon) {
System.out.println(watermelon.obtainName() + "情人节的价格是:" + watermelon.obtainValentinesDayPrice() + "元/斤");
}
@Override
public void visit(Apple apple) {
System.out.println(apple.obtainName() + "情人节的价格是:" + apple.obtainValentinesDayPrice() + "元/斤");
}
}
Main.java
package example;
import java.util.ArrayList;
import java.util.List;
/**
* @program: Test
* @description:
* @author: Lei Dong
* @create: 2019-03-03 10:27
**/
public class Main {
public static void main(String[] args) {
List<IFruit> fruitList = new ArrayList<>();
fruitList.add(new Apple());
fruitList.add(new Watermelon());
Seller seller = new Seller(fruitList);
IVisitor commonVisitor = new CommonVisitor();
seller.showPrice(commonVisitor);
IVisitor springFestivalVisitor = new SpringFestivalVisitor();
seller.showPrice(springFestivalVisitor);
IVisitor valentinesDayVisitor = new ValentinesDayVisitor();
seller.showPrice(valentinesDayVisitor);
}
}
运行结果:
结合UML图来看。
Visitor指代:IVisitor
ConcreteVisitor指代:CommonVisitor、SpringFestivalVisitor、ValentinesDayVisitor
Element指代:IFruit
ConcreteElement指代:Watermelon、Apple
Object Structure指代:Seller
Client指代:Main
五.总结
1.模式优点
(1)使得增加新的访问操作变得很容易。
(2)将有关元素对象的访问行为集中到一个访问者对象中,而不是分散到一个个的元素类中。
(3)可以跨过类的等级结构访问属于不同的等级结构的元素类。
(4)让用户能够在不修改现有类层次结构的情况下,定义该类层次结构的操作。
2.模式缺点
(1)增加新的元素类很困难。在访问者模式中,每增加一个新的元素类都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作,违背了“开闭原则”的要求。
(2)破坏封装。访问者模式要求访问者对象访问并调用每一个元素对象的操作,这意味着元素对象有时候必须暴露一些自己的内部操作和内部状态,否则无法供访问者访问。
3.模式适用场景
(1)一个对象结构包含很多类型的对象,希望对这些对象实施一些依赖其具体类型的操作。在访问者中针对每一种具体的类型都提供了一个访问操作,不同类型的对象可以有不同的访问操作。
(2)需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。访问者模式使得我们可以将相关的访问操作集中起来定义在访问者类中,对象结构可以被多个不同的访问者类所使用,将对象本身与对象的访问操作分离。
(3)对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。