概述
基本介绍
1、访问者模式(Visitor Pattern),封装一些作用于某种数据结构的各个元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
2、主要将数据结构于数据操作分离,解决数据结构和操作耦合的问题。
3、访问者模式的基本工作原理是:在被访问类的里面添加一个对外提供接待访问者的接口。
4、访问者模式主要应用场景是:需要对一个对象结构中的对象进行很多不同操作(这些操作彼此没有什么关联),同时需要避免这些操作污染这些对象的类,可以选用访问者模式。
5、对象结构比较稳定,但经常需要在此对象结构上定义新的操作,使用访问者模式更容易扩展。对象结构不稳定,使用访问者模式会使得程序更加复杂。
6、一句话总结:在结构不变的情况下,动态的改变对于内部元素的访问。
类图
类图说明:
1、Visitor 是抽象访问者,为该对象结构中的ConcreteElement每一个类声明一个Visit操作。
2、ConcreteVisitor:是具体访问者,实现每个Visitor声明的操作,是每个操作具体实现。
3、ObejctStruture:能枚举它的元素,可以提供一个高层的接口,用来允许访问者访问它的元素:
4、Element:定义一个accept()方法,接收一个访问者对象。
5、ConcreteElement:具体元素,实现了accept方法。
代码实现
小需求:组装一台电脑,每个部件针对不同的人群有不同的折扣,比如说,电脑由主机、屏幕、键盘、鼠标构成,结构不再变动。学生人群主机8折、屏幕7折、键盘8折、鼠标8折;公司采购员人群主机6折、屏幕6折、键盘5折、鼠标5折等等。可以使用访问者模式实现,不然就需要在Computer类中添加各种判断,每增加一种人群类型都需要新增一个判断。
根据需求与访问这模式画出类图
代码实现
package com.example.pattern.visitor;
import lombok.Getter;
import lombok.Setter;
/**
* 访问者模式
*/
interface Visitor {
void visitMainframe(Mainframe mainframe);
void visitScreen(Screen screen);
void visitKeyboard(Keyboard keyboard);
void visitMouse(Mouse mouse);
}
@Setter
@Getter
class StudentTypeVisitor implements Visitor {
private Double totalPrice = 0d;
@Override
public void visitMainframe(Mainframe mainframe) {
this.totalPrice += mainframe.getPrice() * 0.8;
}
@Override
public void visitScreen(Screen screen) {
this.totalPrice += screen.getPrice() * 0.9;
}
@Override
public void visitKeyboard(Keyboard keyboard) {
this.totalPrice += keyboard.getPrice() * 0.7;
}
@Override
public void visitMouse(Mouse mouse) {
this.totalPrice += mouse.getPrice() * 0.6;
}
}
@Setter
@Getter
class CorpTypeVisitor implements Visitor {
private Double totalPrice = 0d;
@Override
public void visitMainframe(Mainframe mainframe) {
this.totalPrice += mainframe.getPrice() * 0.7;
}
@Override
public void visitScreen(Screen screen) {
this.totalPrice += screen.getPrice() * 0.6;
}
@Override
public void visitKeyboard(Keyboard keyboard) {
this.totalPrice += keyboard.getPrice() * 0.5;
}
@Override
public void visitMouse(Mouse mouse) {
this.totalPrice += mouse.getPrice() * 0.5;
}
}
@Getter
@Setter
public class Computer { // Computer结构已经被固定 就是这四个模块
private ComputerPart mainframe = new Mainframe(); //主机
private ComputerPart screen = new Screen(); // 屏幕
private ComputerPart keyboard = new Keyboard(); // 键盘
private ComputerPart mouse = new Mouse(); // 鼠标
public void accept(Visitor visitor) {
this.mainframe.accept(visitor);
this.screen.accept(visitor);
this.keyboard.accept(visitor);
this.mouse.accept(visitor);
}
}
abstract class ComputerPart { // 抽象类 电脑的一部分
abstract void accept(Visitor visitor);
abstract Double getPrice();
}
class Mainframe extends ComputerPart { // 主机
@Override
public void accept(Visitor visitor) {
visitor.visitMainframe(this);
}
@Override
public Double getPrice() {
return 5000d;
}
}
class Screen extends ComputerPart { // 屏幕
@Override
public void accept(Visitor visitor) {
visitor.visitScreen(this);
}
@Override
public Double getPrice() {
return 900d;
}
}
class Keyboard extends ComputerPart { // 键盘
@Override
public void accept(Visitor visitor) {
visitor.visitKeyboard(this);
}
@Override
public Double getPrice() {
return 60d;
}
}
class Mouse extends ComputerPart { // 鼠标
@Override
public void accept(Visitor visitor) {
visitor.visitMouse(this);
}
@Override
public Double getPrice() {
return 50d;
}
}
class Client {
public static void main(String[] args) {
Computer computer = new Computer();
StudentTypeVisitor studentTypeVisitor = new StudentTypeVisitor();
computer.accept(studentTypeVisitor);
System.out.println(studentTypeVisitor.getTotalPrice());
CorpTypeVisitor corpTypeVisitor = new CorpTypeVisitor();
computer.accept(corpTypeVisitor);
System.out.println(corpTypeVisitor.getTotalPrice());
// 经过代码实现在添加其它类型的人群是比较简单的,只需要实现 Visitor接口实现方法即可
// 但是如何Computer类结构有变化 比如有耳机模块,整个模式将不再好维护,变得比较复杂。
}
}
使用细节
优点
1、访问这模式符合单一职责原则,让程序具有更好的扩展性。
2、访问者模式可以对功能进行统一,可以做报表等数据结构非常稳定的系统。
缺点
1、具体元素对访问者公布细节,也就是说访问者关注了其它类的内部细节,不符合迪米特法则,这样也就造成了具体元素的变更比较困难,修改起来比较麻烦。
2、违背了依赖倒转原则,访问者依赖的是具体元素,而不是抽象元素。
3、因此访问者模式适合于拥有稳定的数据结构、但是功能经常变化的系统。