设计模式-访问者模式

参考:http://c.biancheng.net/view/1397.html

什么是访问者模式

​ 访问者(Visitor)模式的定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。

优点:

  1. 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
  2. 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
  3. 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
  4. 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。

缺点:

  1. 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
  2. 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
  3. 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。

结构与实现

​ 访问者(Visitor)模式实现的关键是如何将作用于元素的操作分离出来封装成独立的类,其基本结构与实现方法如下。

访问者模式包含以下主要角色。

  1. 抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
  2. 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
  3. 抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
  4. 具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
  5. 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。

1.png

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74

public class VisitorPattern {
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);
}
}
//抽象访问者
interface Visitor {
void visit(ConcreteElementA element);
void visit(ConcreteElementB element);
}
//具体访问者A类
class ConcreteVisitorA implements Visitor {
public void visit(ConcreteElementA element) {
System.out.println("具体访问者A访问-->" + element.operationA());
}
public void visit(ConcreteElementB element) {
System.out.println("具体访问者A访问-->" + element.operationB());
}
}
//具体访问者B类
class ConcreteVisitorB implements Visitor {
public void visit(ConcreteElementA element) {
System.out.println("具体访问者B访问-->" + element.operationA());
}
public void visit(ConcreteElementB element) {
System.out.println("具体访问者B访问-->" + element.operationB());
}
}
//抽象元素类
interface Element {
void accept(Visitor visitor);
}
//具体元素A类
class ConcreteElementA implements Element {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String operationA() {
return "具体元素A的操作。";
}
}
//具体元素B类
class ConcreteElementB implements Element {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String operationB() {
return "具体元素B的操作。";
}
}
//对象结构角色
class ObjectStructure {
private List<Element> list = new ArrayList<Element>();
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);
}
}

运行结果:

1
2
3
4
5
具体访问者A访问-->具体元素A的操作。
具体访问者A访问-->具体元素B的操作。
------------------------
具体访问者B访问-->具体元素A的操作。
具体访问者B访问-->具体元素B的操作。

应用实例

【例1】利用“访问者(Visitor)模式”模拟艺术公司与造币公司的功能。

分析:艺术公司利用“铜”可以设计出铜像,利用“纸”可以画出图画;造币公司利用“铜”可以印出铜币,利用“纸”可以印出纸币(点此下载运行该程序后所要显示的图片)。对“铜”和“纸”这两种元素,两个公司的处理方法不同,所以该实例用访问者模式来实现比较适合。

首先,定义一个公司(Company)接口,它是抽象访问者,提供了两个根据纸(Paper)或铜(Cuprum)这两种元素创建作品的方法;再定义艺术公司(ArtCompany)类和造币公司(Mint)类,它们是具体访问者,实现了父接口的方法。

然后,定义一个材料(Material)接口,它是抽象元素,提供了 accept(Company visitor)方法来接受访问者(Company)对象访问;再定义纸(Paper)类和铜(Cuprum)类,它们是具体元素类,实现了父接口中的方法。

最后,定义一个材料集(SetMaterial)类,它是对象结构角色,拥有保存所有元素的容器 List,并提供让访问者对象遍历容器中的所有元素的 accept(Company visitor)方法;客户类设计成窗体程序,它提供材料集(SetMaterial)对象供访问者(Company)对象访问,实现了 ItemListener 接口,处理用户的事件请求。

2.png

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
public class VisitorProducer {
public static void main(String[] args) {
new MaterialWin();
}
}
//窗体类
class MaterialWin extends JFrame implements ItemListener {
private static final long serialVersionUID = 1L;
JPanel CenterJP;
SetMaterial os; //材料集对象
Company visitor1, visitor2; //访问者对象
String[] select;
MaterialWin() {
super("利用访问者模式设计艺术公司和造币公司");
JRadioButton Art;
JRadioButton mint;
os = new SetMaterial();
os.add(new Cuprum());
os.add(new Paper());
visitor1 = new ArtCompany();//艺术公司
visitor2 = new Mint(); //造币公司
this.setBounds(10, 10, 750, 350);
this.setResizable(false);
CenterJP = new JPanel();
this.add("Center", CenterJP);
JPanel SouthJP = new JPanel();
JLabel yl = new JLabel("原材料有:铜和纸,请选择生产公司:");
Art = new JRadioButton("艺术公司", true);
mint = new JRadioButton("造币公司");
Art.addItemListener(this);
mint.addItemListener(this);
ButtonGroup group = new ButtonGroup();
group.add(Art);
group.add(mint);
SouthJP.add(yl);
SouthJP.add(Art);
SouthJP.add(mint);
this.add("South", SouthJP);
select = (os.accept(visitor1)).split(" "); //获取产品名
showPicture(select[0], select[1]); //显示产品
}
//显示图片
void showPicture(String Cuprum, String paper) {
CenterJP.removeAll(); //清除面板内容
CenterJP.repaint(); //刷新屏幕
String FileName1 = "src/visitor/Picture/" + Cuprum + ".jpg";
String FileName2 = "src/visitor/Picture/" + paper + ".jpg";
JLabel lb = new JLabel(new ImageIcon(FileName1), JLabel.CENTER);
JLabel rb = new JLabel(new ImageIcon(FileName2), JLabel.CENTER);
CenterJP.add(lb);
CenterJP.add(rb);
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
@Override
public void itemStateChanged(ItemEvent arg0) {
JRadioButton jc = (JRadioButton) arg0.getSource();
if (jc.isSelected()) {
if (jc.getText() == "造币公司") {
select = (os.accept(visitor2)).split(" ");
} else {
select = (os.accept(visitor1)).split(" ");
}
showPicture(select[0], select[1]); //显示选择的产品
}
}
}
//抽象访问者:公司
interface Company {
String create(Paper element);
String create(Cuprum element);
}
//具体访问者:艺术公司
class ArtCompany implements Company {
public String create(Paper element) {
return "讲学图";
}
public String create(Cuprum element) {
return "朱熹铜像";
}
}
//具体访问者:造币公司
class Mint implements Company {
public String create(Paper element) {
return "纸币";
}
public String create(Cuprum element) {
return "铜币";
}
}
//抽象元素:材料
interface Material {
String accept(Company visitor);
}
//具体元素:纸
class Paper implements Material {
public String accept(Company visitor) {
return (visitor.create(this));
}
}
//具体元素:铜
class Cuprum implements Material {
public String accept(Company visitor) {
return (visitor.create(this));
}
}
//对象结构角色:材料集
class SetMaterial {
private List<Material> list = new ArrayList<Material>();
public String accept(Company visitor) {
Iterator<Material> i = list.iterator();
String tmp = "";
while (i.hasNext()) {
tmp += ((Material) i.next()).accept(visitor) + " ";
}
return tmp; //返回某公司的作品集
}
public void add(Material element) {
list.add(element);
}
public void remove(Material element) {
list.remove(element);
}
}

运行结果:

3.png

【例2】这是我自己用访问者模式模拟出不同客户购买电脑所得价格的功能。

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 抽象访问者
public interface Visitor {
void visitCPU(CPU cpu ,int count);
void visitComputerCase(ComputerCase computerCaseint ,int count);
void visitCpuHeatSink(CpuHeatSink cpuHeatSinkint ,int count);
void visitFan(Fan Fanint ,int count);
void visitGraphicsCard(GraphicsCard GraphicsCardint ,int count);
void visitMainboard(Mainboard mainboardint ,int count);
void visitMemoryBank(MemoryBank memoryBankint ,int count);
void visitPowerSupply(PowerSupply powerSupplyint ,int count);
void visitSSD(SSD ssdint ,int count);
double getTotalPrice();
}
1
2
3
4
5
// 抽象元素
public interface ComputerPart {
void accept(Visitor visitor);
double getPrice();
}
1
2
3
4
5
6
7
8
9
10
11
12
// 机箱
public class ComputerCase implements ComputerPart {
@Override
public void accept(Visitor visitor) {
visitor.visitComputerCase(this,1);
}

@Override
public double getPrice() {
return 800;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
// CPU
public class CPU implements ComputerPart {
@Override
public void accept(Visitor visitor) {
visitor.visitCPU(this,1);
}

@Override
public double getPrice() {
return 3000;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
// CPU散热器
public class CpuHeatSink implements ComputerPart {
@Override
public void accept(Visitor visitor) {
visitor.visitCpuHeatSink(this,1);
}

@Override
public double getPrice() {
return 500;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
// 风扇
public class Fan implements ComputerPart {
@Override
public void accept(Visitor visitor) {
visitor.visitFan(this,7);
}

@Override
public double getPrice() {
return 30;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
// 显卡
public class GraphicsCard implements ComputerPart {
@Override
public void accept(Visitor visitor) {
visitor.visitGraphicsCard(this,1);
}

@Override
public double getPrice() {
return 4800;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
// 主板
public class Mainboard implements ComputerPart {
@Override
public void accept(Visitor visitor) {
visitor.visitMainboard(this,1);
}

@Override
public double getPrice() {
return 2000;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
// 内存条
public class MemoryBank implements ComputerPart {
@Override
public void accept(Visitor visitor) {
visitor.visitMemoryBank(this,2);
}

@Override
public double getPrice() {
return 500;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
// 电源
public class PowerSupply implements ComputerPart {
@Override
public void accept(Visitor visitor) {
visitor.visitPowerSupply(this,1);
}

@Override
public double getPrice() {
return 800;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
// 固态硬盘
public class SSD implements ComputerPart {
@Override
public void accept(Visitor visitor) {
visitor.visitSSD(this,2);
}

@Override
public double getPrice() {
return 800;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
// 访问者-学生
public class StudentVisitor implements Visitor{

/**
* 折扣
*/
private double discounts;

/**
* 总价
*/
private double totalPrice;

public StudentVisitor(double discounts){
this.discounts = discounts;
}

@Override
public void visitCPU(CPU cpu,int count){
double v = cpu.getPrice() * discounts;
totalPrice+=v*count;
System.out.print("cpu:原价:"+cpu.getPrice());
System.out.println(" 优惠价:"+v);
}

@Override
public void visitComputerCase(ComputerCase computerCase,int count){
double v = computerCase.getPrice() * discounts;
totalPrice+=v*count;
System.out.print("机箱:原价:"+computerCase.getPrice());
System.out.println(" 优惠价:"+v);
}

@Override
public void visitCpuHeatSink(CpuHeatSink cpuHeatSink,int count){
double v = cpuHeatSink.getPrice() * discounts;
totalPrice+=v*count;
System.out.print("CPU散热器:原价:"+cpuHeatSink.getPrice());
System.out.println(" 优惠价:"+v);
}

@Override
public void visitFan(Fan fan,int count){
double v = fan.getPrice() * discounts;
totalPrice+=v*count;
System.out.print("风扇:原价:"+fan.getPrice());
System.out.println(" 优惠价:"+v);
}

@Override
public void visitGraphicsCard(GraphicsCard graphicsCard,int count){
double v = graphicsCard.getPrice() * discounts;
totalPrice+=v*count;
System.out.print("显卡:原价:"+graphicsCard.getPrice());
System.out.println(" 优惠价:"+v);
}

@Override
public void visitMainboard(Mainboard mainboard,int count){
double v = mainboard.getPrice() * discounts;
totalPrice+=v*count;
System.out.print("主板:原价:"+mainboard.getPrice());
System.out.println(" 优惠价:"+v);
}

@Override
public void visitMemoryBank(MemoryBank memoryBank,int count){
double v = memoryBank.getPrice() * discounts;
totalPrice+=v*count;
System.out.print("内存条:原价:"+memoryBank.getPrice());
System.out.println(" 优惠价:"+v);
}

@Override
public void visitPowerSupply(PowerSupply powerSupply,int count){
double v = powerSupply.getPrice() * discounts;
totalPrice+=v*count;
System.out.print("电源:原价:"+powerSupply.getPrice());
System.out.println(" 优惠价:"+v);
}

@Override
public void visitSSD(SSD ssd,int count){
double v = ssd.getPrice() * discounts;
totalPrice+=v*count;
System.out.print("固态硬盘:原价:"+ssd.getPrice());
System.out.println(" 优惠价:"+v);
}

@Override
public double getTotalPrice(){
return this.totalPrice;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
// 访问者-工作者
public class WorkVisitor implements Visitor{

/**
* 折扣
*/
private double discounts;

/**
* 总价
*/
private double totalPrice;

public WorkVisitor(double discounts){
this.discounts = discounts;
}

@Override
public void visitCPU(CPU cpu,int count){
double v = cpu.getPrice() * discounts;
totalPrice+=v*count;
System.out.print("cpu:原价:"+cpu.getPrice());
System.out.println(" 优惠价:"+v);
}

@Override
public void visitComputerCase(ComputerCase computerCase,int count){
double v = computerCase.getPrice() * discounts;
totalPrice+=v*count;
System.out.print("机箱:原价:"+computerCase.getPrice());
System.out.println(" 优惠价:"+v);
}

@Override
public void visitCpuHeatSink(CpuHeatSink cpuHeatSink,int count){
double v = cpuHeatSink.getPrice() * discounts;
totalPrice+=v*count;
System.out.print("CPU散热器:原价:"+cpuHeatSink.getPrice());
System.out.println(" 优惠价:"+v);
}

@Override
public void visitFan(Fan fan,int count){
double v = fan.getPrice() * discounts;
totalPrice+=v*count;
System.out.print("风扇:原价:"+fan.getPrice());
System.out.println(" 优惠价:"+v);
}

@Override
public void visitGraphicsCard(GraphicsCard graphicsCard,int count){
double v = graphicsCard.getPrice() * discounts;
totalPrice+=v*count;
System.out.print("显卡:原价:"+graphicsCard.getPrice());
System.out.println(" 优惠价:"+v);
}

@Override
public void visitMainboard(Mainboard mainboard,int count){
double v = mainboard.getPrice() * discounts;
totalPrice+=v*count;
System.out.print("主板:原价:"+mainboard.getPrice());
System.out.println(" 优惠价:"+v);
}

@Override
public void visitMemoryBank(MemoryBank memoryBank,int count){
double v = memoryBank.getPrice() * discounts;
totalPrice+=v*count;
System.out.print("内存条:原价:"+memoryBank.getPrice());
System.out.println(" 优惠价:"+v);
}

@Override
public void visitPowerSupply(PowerSupply powerSupply,int count){
double v = powerSupply.getPrice() * discounts;
totalPrice+=v*count;
System.out.print("电源:原价:"+powerSupply.getPrice());
System.out.println(" 优惠价:"+v);
}

@Override
public void visitSSD(SSD ssd,int count){
double v = ssd.getPrice() * discounts;
totalPrice+=v*count;
System.out.print("固态硬盘:原价:"+ssd.getPrice());
System.out.println(" 优惠价:"+v);
}

@Override
public double getTotalPrice(){
return this.totalPrice;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 对象结构角色
public class ObjectStructure {
private List<ComputerPart> list = new ArrayList<>();
public void accept(Visitor visitor) {
Iterator<ComputerPart> i = list.iterator();
while (i.hasNext()) {
(i.next()).accept(visitor);
}
}
public void add(ComputerPart part) {
list.add(part);
}
public void remove(ComputerPart part) {
list.remove(part);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 客户端
public class Computer {
public static void main(String[] args) {
ObjectStructure os = new ObjectStructure();
os.add(new CPU());
os.add(new CpuHeatSink());
os.add(new Mainboard());
os.add(new MemoryBank());
os.add(new SSD());
os.add(new GraphicsCard());
os.add(new PowerSupply());
os.add(new Fan());
os.add(new ComputerCase());

System.out.println("************学生优惠************");
Visitor studentVisitor = new StudentVisitor(0.75);
os.accept(studentVisitor);
System.out.println("总价:"+studentVisitor.getTotalPrice());
System.out.println();
System.out.println("************职工优惠************");
Visitor workVisitor = new WorkVisitor(0.9);
os.accept(workVisitor);
System.out.println("总价:"+workVisitor.getTotalPrice());
}
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
************学生优惠************
cpu:原价:3000.0 优惠价:2250.0
CPU散热器:原价:500.0 优惠价:375.0
主板:原价:2000.0 优惠价:1500.0
内存条:原价:500.0 优惠价:375.0
固态硬盘:原价:800.0 优惠价:600.0
显卡:原价:4800.0 优惠价:3600.0
电源:原价:800.0 优惠价:600.0
风扇:原价:30.0 优惠价:22.5
机箱:原价:800.0 优惠价:600.0
总价:11032.5

************职工优惠************
cpu:原价:3000.0 优惠价:2700.0
CPU散热器:原价:500.0 优惠价:450.0
主板:原价:2000.0 优惠价:1800.0
内存条:原价:500.0 优惠价:450.0
固态硬盘:原价:800.0 优惠价:720.0
显卡:原价:4800.0 优惠价:4320.0
电源:原价:800.0 优惠价:720.0
风扇:原价:30.0 优惠价:27.0
机箱:原价:800.0 优惠价:720.0
总价:13239.0

应用场景

​ 当系统中存在类型数量稳定(固定)的一类数据结构时,可以使用访问者模式方便地实现对该类型所有数据结构的不同操作,而又不会对数据产生任何副作用(脏数据)。

简而言之,就是当对集合中的不同类型数据(类型数量稳定)进行多种操作时,使用访问者模式。

通常在以下情况可以考虑使用访问者(Visitor)模式。

  1. 对象结构相对稳定,但其操作算法经常变化的程序。
  2. 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
  3. 对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。

扩展

访问者(Visitor)模式是使用频率较高的一种设计模式,它常常同以下两种设计模式联用。

(1)与“迭代器模式”联用。因为访问者模式中的“对象结构”是一个包含元素角色的容器,当访问者遍历容器中的所有元素时,常常要用迭代器。如【例1】中的对象结构是用 List 实现的,它通过 List 对象的 Iterator() 方法获取迭代器。如果对象结构中的聚合类没有提供迭代器,也可以用迭代器模式自定义一个。

(2)访问者(Visitor)模式同“组合模式”联用。因为访问者(Visitor)模式中的“元素对象”可能是叶子对象或者是容器对象,如果元素对象包含容器对象,就必须用到组合模式

4.png