1、问题引入
在软件开发过程中,遍历集合的操作是访问操作的一种,且我们经常把遍历和其他的逻辑混合在一起,且当我更换遍历方式时,我还需要修改代码,因此,我们思考,可不可以将访问操作独立出来变成一个新的类,当我们需要增加访问操作的时候,直接增加新的类,原来的代码不需要任何的改变。
而访问者模式恰好可以实现这个,使得使用不同的访问方式都可以对某些元素进行访问。
二、定义
将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。
三、意图
表示一个作用于某对象结构中的各个元素的操作。它可以在不改变各元素的类的前提下定义作用于这些元素的新的操作。
四、使用场景
1、对象结构中对象对应的类较少改变、但是会经常在此对象结构上定义新的操作
2、需要对一个对象结构中进行一些不相关的操作、需要在新增操作时避免改变其原来的类
五、优缺点
优点
1、符合单一职责原则。每个类负责一个职责
2、具有优秀的扩展性和灵活性、添加新的操作会变得较为容易、同时也不会改变其原来的结构代码
3、访问者模式将一些相关的行为操作集合在了访问者对象中,并没有分散在其元素类中
缺点
1、具体元素对访问者公开了细节,违背了迪米特原则
2、增加具体元素节点变得困难、与之随之增加的就是在访问者中新增。
六、包含角色
- 抽象访问者(Visitor)角色:声明了一个或者多个方法操作,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
- 具体访问者(ConcreteVisitor)角色:实现抽象访问者所声明的接口,也就是抽象访问者所声明的各个访问操作,确定访问者访问一个元素时该做什么。
- 抽象元素(Element)角色:声明一个接受操作,接受一个访问者对象作为一个参数,被接受的访问者对象作为 accept() 方法的参数。
- 具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
- 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现
七、代码结构
package visitor.v1;
/**
* @Package: visitor.v1
* @ClassName: Visitor
* @Author: tanp
* @Description: 抽象访问者(Visitor)角色,声明了一个或者多个方法操作,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
* @Date: 2020/11/26 11:21
*/
public interface Visitor {
void visit(ConcreteElementA concreteElementA);
void visit(ConcreteElementB concreteElementB);
}
package visitor.v1;
/**
* @Package: visitor.v1
* @ClassName: ConcreteVisitorA
* @Author: tanp
* @Description: 具体访问者(ConcreteVisitor)角色:实现抽象访问者所声明的接口,也就是抽象访问者所声明的各个访问操作,确定访问者访问一个元素时该做什么
* @Date: 2020/11/26 11:36
*/
public class ConcreteVisitorA implements Visitor{
@Override
public void visit(ConcreteElementA concreteElementA) {
System.out.println("访问者A访问A元素");
}
@Override
public void visit(ConcreteElementB concreteElementB) {
System.out.println("访问者A访问B元素");
}
}
package visitor.v1;
/**
* @Package: visitor.v1
* @ClassName: ConcreteVisitorB
* @Author: tanp
* @Description: 具体访问者(ConcreteVisitor)角色:实现抽象访问者所声明的接口,也就是抽象访问者所声明的各个访问操作,确定访问者访问一个元素时该做什么。
* @Date: 2020/11/26 11:42
*/
public class ConcreteVisitorB implements Visitor{
@Override
public void visit(ConcreteElementA concreteElementA) {
System.out.println("访问者B访问A元素");
}
@Override
public void visit(ConcreteElementB concreteElementB) {
System.out.println("访问者B访问B元素");
}
}
package visitor.v1;
/**
* @Package: visitor.v1
* @ClassName: Element
* @Author: tanp
* @Description: 抽象元素(Element)角色:声明一个接受操作,接受一个访问者对象作为一个参数,被接受的访问者对象作为 accept() 方法的参数。
* @Date: 2020/11/26 11:26
*/
public interface Element {
/**接受观察者的访问方法*/
void accept(Visitor visitor);
}
package visitor.v1;
/**
* @Package: visitor.v1
* @ClassName: ConcreteElementA
* @Author: tanp
* @Description: 具体元素角色,实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作
* @Date: 2020/11/26 11:23
*/
public class ConcreteElementA implements Element{
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
package visitor.v1;
/**
* @Package: visitor.v1
* @ClassName: ConcreteElementB
* @Author: tanp
* @Description: 具体元素角色,实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作
* @Date: 2020/11/26 11:23
*/
public class ConcreteElementB implements Element{
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
package visitor.v1;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @Package: visitor.v1
* @ClassName: ObjectStructure
* @Author: tanp
* @Description: 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现
* @Date: 2020/11/26 11:47
*/
public class ObjectStructure {
List<Element> list = new ArrayList<Element>();
public void add(Element element){
list.add(element);
}
public void remove(Element element){
list.remove(element);
}
/**遍历所有元素,将每个调用接受观察者访问的方法*/
public void accept(Visitor visitor){
Iterator<Element> iterator = list.iterator();
while (iterator.hasNext()){
iterator.next().accept(visitor);
}
}
}
package visitor.v1;
/**
* @Package: visitor.v1
* @ClassName: DemoClient
* @Author: tanp
* @Description: ${description}
* @Date: 2020/11/26 15:34
*/
public class DemoClient {
public static void main(String[] args) {
//新建对象结构实例
ObjectStructure objectStructure = new ObjectStructure();
//添加元素
objectStructure.add(new ConcreteElementA());
objectStructure.add(new ConcreteElementB());
//新建观察者
Visitor visitor = new ConcreteVisitorA();
objectStructure.accept(visitor);
visitor = new ConcreteVisitorB();
objectStructure.accept(visitor);
}
}
八、代码示例
我们现在来编写一个案例,就是一个补习班上3有个同学,1个老师,补习的科目有语数外三科,老师这次组织了一次考试,考试的成绩已经出来了,现在的要求是编写一个访问者模式的代码系统,实现老师访问系统的时候,可以看到每个学生的各个分数和学生总分数,学生则是看到自己单独的各科分数,下面开始编码
package visitor.v2;
/**
* @Package: visitor.v2
* @ClassName: Visitor
* @Author: tanp
* @Description: 抽象访问者(Visitor)角色,声明了一个或者多个方法操作,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
* @Date: 2020/11/26 17:16
*/
public abstract class Visitor {
protected String name;
public Visitor(String name){
this.name = name;
}
abstract int visit(English english);
abstract int visit(Math math);
abstract int visit(Chinese chinese);
}
package visitor.v2;
/**
* @Package: visitor.v2
* @ClassName: Teacher
* @Author: tanp
* @Description: 具体访问者(ConcreteVisitor)角色:实现抽象访问者所声明的接口,也就是抽象访问者所声明的各个访问操作,确定访问者访问一个元素时该做什么
* @Date: 2020/11/26 16:30
*/
public class Teacher extends Visitor {
public Teacher(String name) {
super(name);
}
@Override
public int visit(English english) {
System.out.println("name:" + english.StudentName + ",english:" + english.score);
return english.score;
}
@Override
public int visit(Math math) {
System.out.println("name:" + math.StudentName + ",math:" + math.score);
return math.score;
}
@Override
public int visit(Chinese chinese) {
System.out.println("name:" + chinese.StudentName + ",chinese:" + chinese.score);
return chinese.score;
}
}
package visitor.v2;
/**
* @Package: visitor.v2
* @ClassName: Student
* @Author: tanp
* @Description: 具体访问者(ConcreteVisitor)角色:实现抽象访问者所声明的接口,也就是抽象访问者所声明的各个访问操作,确定访问者访问一个元素时该做什么
* @Date: 2020/11/26 16:28
*/
public class Student extends Visitor {
public Student(String name) {
super(name);
}
@Override
public int visit(English english) {
if (name.equals(english.StudentName)) {
System.out.println("name:" + name + ",english:" + english.score);
}
return english.score;
}
@Override
public int visit(Math math) {
if (name.equals(math.StudentName)) {
System.out.println("name:" + name + ",math:" + math.score);
}
return math.score;
}
@Override
public int visit(Chinese chinese) {
if (name.equals(chinese.StudentName)) {
System.out.println("name:" + name + ",chinese:" + chinese.score);
}
return chinese.score;
}
}
package visitor.v2;
import java.util.Random;
/**
* @Package: visitor.v2
* @ClassName: Subjects
* @Author: tanp
* @Description: 抽象元素(Element)角色:声明一个接受操作,接受一个访问者对象作为一个参数,被接受的访问者对象作为 accept() 方法的参数。
* @Date: 2020/11/26 16:17
*/
public abstract class Subjects {
/**学生姓名*/
protected String StudentName;
/**学生分数*/
protected int score;
public Subjects(String studentName){
this.StudentName = studentName;
score = new Random().nextInt(100);
}
public abstract int accept(Visitor visitor);
}
package visitor.v2;
/**
* @Package: visitor.v2
* @ClassName: Chinese
* @Author: tanp
* @Description: 具体元素角色,实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作
* @Date: 2020/11/26 16:32
*/
public class Chinese extends Subjects{
public Chinese(String studentName) {
super(studentName);
}
@Override
public int accept(Visitor visitor) {
return visitor.visit(this);
}
}
package visitor.v2;
/**
* @Package: visitor.v2
* @ClassName: English
* @Author: tanp
* @Description: 具体元素角色,实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作
* @Date: 2020/11/26 16:15
*/
public class English extends Subjects {
public English(String studentName) {
super(studentName);
}
@Override
public int accept(Visitor visitor) {
return visitor.visit(this);
}
}
package visitor.v2;
/**
* @Package: visitor.v2
* @ClassName: Math
* @Author: tanp
* @Description: 具体元素角色,实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作
* @Date: 2020/11/26 16:31
*/
public class Math extends Subjects{
public Math(String studentName) {
super(studentName);
}
@Override
public int accept(Visitor visitor) {
return visitor.visit(this);
}
}
package visitor.v2;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @Package: visitor.v2
* @ClassName: ObjectStructure
* @Author: tanp
* @Description: 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现
* @Date: 2020/11/26 16:48
*/
public class ObjectStructure {
List<Subjects> list = new ArrayList<Subjects>();
public void add(Subjects subject) {
list.add(subject);
}
/**
* 遍历所有元素,将每个调用接受观察者访问的方法
*/
public void accept(Visitor visitor) {
String name = visitor.name.substring(0, 4);
//单科成绩
int score = 0;
//总分
int sum = 0;
int index = 0;
for (Subjects subjects : list) {
score = subjects.accept(visitor);
//以下逻辑为老师查看学生总分所写,但是代码逻辑要求list中的学生成绩必须是按一个一个学生添加得来
if (name.equals("miss")) {
sum = sum + score;
index++;
if (index == 3) {
System.out.println(subjects.StudentName + "总分:" + sum);
index = 0;
}
}
}
}
}
package visitor.v2;
import proxy.Subject;
/**
* @Package: visitor.v2
* @ClassName: DemoClient
* @Author: tanp
* @Description: ${description}
* @Date: 2020/11/26 17:03
*/
public class DemoClient {
public static void main(String[] args) {
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.add(new English("mike"));
objectStructure.add(new Chinese("mike"));
objectStructure.add(new Math("mike"));
objectStructure.add(new English("lucy"));
objectStructure.add(new Chinese("lucy"));
objectStructure.add(new Math("lucy"));
Visitor visitor = new Student("mike");
System.out.println("学生mike查看成绩");
objectStructure.accept(visitor);
visitor = new Teacher("miss zhang");
System.out.println("老师查看成绩");
objectStructure.accept(visitor);
}
}
九、总结
访问者模式就介绍到这里啦。访问者模式主要是将数据结构及操作分离、解决了稳定的数据结构和容易变化的操作的耦合性。在新增操作的时候不修改原来的结构对象的类。直接修改访问者对象中的操作即可新增操作。