访问者模式是一个相对比较简单,但结构又稍显复杂的模式,它讲的是表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。例如,你在朋友家做客,你是访问者,朋友接收你的访问,你通过朋友的描述,然后对朋友的描述做出一个判断,这就是访问者模式。
访问者模式(visitor),封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。uml结构图如下:
其中,visitor是抽象访问者,为该对象结构中concreteelement的每一个类声明一个visit操作;concretevisitor是具体访问者,实现每个由visitor声明的操作,是每个操作实现算法的一部分,而该算法片段是对应于结构中对象的类;objectstructure为能枚举它的元素,可以提供一个高层的接口以允许访问者访问它的元素;element定义了一个accept操作,它以一个访问者为参数;concreteelement为具体元素,实现accept操作。
此处可为抽象类或接口,用于声明访问者可以访问哪些元素,具体到程序中就是visit方法的参数定义哪些对象是可以被访问的。
1 public abstract class visitor { 2 3 public abstract void visitconcreteelementa(concreteelementa concreteelementa); 4 5 public abstract void visitconcreteelementb(concreteelementb concreteelementb); 6 7 }
影响访问者访问到一个类后该干什么、怎么干。这里以concretevisitor1为例,concretevisitor2就不再赘述了。
1 public class concretevisitor1 extends visitor { 2 3 @override 4 public void visitconcreteelementa(concreteelementa concreteelementa) { 5 system.out.println(concreteelementa.getclass().getname() + " 被 " + this.getclass().getname() + " 访问"); 6 } 7 8 @override 9 public void visitconcreteelementb(concreteelementb concreteelementb) { 10 system.out.println(concreteelementb.getclass().getname() + " 被 " + this.getclass().getname() + " 访问"); 11 } 12 13 }
此处为接口后抽象类,用于声明接受哪一类访问者访问,程序上是通过accpet方法中的参数来定义的。
抽象元素有两类方法,一是本身的业务逻辑,也就是元素作为一个业务处理单元必须完成的职责;另外一个是允许哪一个访问者来访问。这里只声明的第二类即accept方法。
1 public abstract class element { 2 3 public abstract void accept(visitor visitor); 4 5 }
实现accept方法,通常是visitor.visit(this)。这里以concreteelementa为例,concreteelementb就不再赘述了。
1 public class concreteelementa extends element { 2 3 @override 4 public void accept(visitor visitor) { 5 visitor.visitconcreteelementa(this); 6 } 7 8 //其它方法 9 public void operationa() { 10 11 } 12 13 }
元素生产者,一般容纳在多个不同类、不同接口的容器,如list、set、map等,在项目中,一般很少抽象出这个角色。
1 public class objectstructure { 2 3 private list<element> elements = new linkedlist<>(); 4 5 public void attach(element element) { 6 elements.add(element); 7 } 8 9 public void detach(element element) { 10 elements.remove(element); 11 } 12 13 public void accept(visitor visitor) { 14 for (element element : elements) { 15 element.accept(visitor); 16 } 17 } 18 19 }
我们通过以下场景模拟一下访问者模式。
1 public class client { 2 3 public static void main(string[] args) { 4 objectstructure objectstructure = new objectstructure(); 5 6 objectstructure.attach(new concreteelementa()); 7 objectstructure.attach(new concreteelementb()); 8 9 concretevisitor1 visitor1 = new concretevisitor1(); 10 concretevisitor2 visitor2 = new concretevisitor2(); 11 12 objectstructure.accept(visitor1); 13 objectstructure.accept(visitor2); 14 } 15 16 }
运行结果如下:
下面就以上述应用实例中的人类分为男人和女人这个例子来实现访问者模式。uml图如下:
抽象的状态类,主要声明以下两个方法。
这里的关键在于人只分男人和女人,这个性别的分类是稳定的,所以可以在状态类中,增加“男人反应”和“女人反应”两个方法,方法个数是稳定的,不会容易发生变化。
1 public abstract class action { 2 3 //得到男人的结论或反应 4 public abstract void getmanconclusion(man man); 5 6 //得到女人的结论或反应 7 public abstract void getwomanconclusion(woman woman); 8 9 }
人的抽象类。只有一个“接受”的抽象方法,它是用来获得“状态”对象的。
1 public abstract class person { 2 3 //接受 4 public abstract void accept(action action); 5 6 }
这里以成功类(success)为例,失败类(fail)同理。
1 public class success extends action { 2 3 @override 4 public void getmanconclusion(man man) { 5 system.out.println("男人成功..."); 6 } 7 8 @override 9 public void getwomanconclusion(woman woman) { 10 system.out.println("女人成功..."); 11 } 12 13 }
这里以男人类(man)为例,女人类(woman)同理。
这里用到了双分派,即首先在客户程序中将具体状态作为参数传递给man类完成了一次分派,然后man类调用作为参数的“具体方法”中的方法getmanconclusion(),同时将自己(this)作为参数传递进去,这便完成了第二次分派。accept方法就是一个双分派操作,它得到执行的操作不仅决定于action类的具体状态,还决定于它访问的person的类别。
1 public class man extends person { 2 3 @override 4 public void accept(action action) { 5 action.getmanconclusion(this); 6 } 7 8 }
1 public class objectstructure { 2 3 private list<person> elements = new linkedlist<>(); 4 5 //增加 6 public void attach(person person) { 7 elements.add(person); 8 } 9 10 //移除 11 public void detach(person person) { 12 elements.remove(person); 13 } 14 15 //查看显示 16 public void display(action action) { 17 for (person person : elements) { 18 person.accept(action); 19 } 20 } 21 22 }
1 public class client { 2 3 public static void main(string[] args) { 4 objectstructure objectstructure = new objectstructure(); 5 6 objectstructure.attach(new man()); 7 objectstructure.attach(new woman()); 8 9 //成功 10 success success = new success(); 11 objectstructure.display(success); 12 13 //失败 14 failing failing = new failing(); 15 objectstructure.display(failing); 16 } 17 18 }
运行结果如下:
上面提到了双分派,所谓双分派是指不管类怎么变化,我们都能找到期望的方法运行。双分派意味着得到执行的操作取决于请求的种类和两个接收者的类型。
以上述实例为例,假设我们要添加一个marray的状态类来考察man类和woman类的反应,由于使用了双分派,只需增加一个action子类即可在客户端调用来查看,不需要改动任何其他类的代码。
而单分派语言处理一个操作是根据请求者的名称和接收到的参数决定的,在java中有静态绑定和动态绑定之说,它的实现是依据重载和重写实现的。值得一提的是,java是一个支持双分派的单分派语言。
源码地址:https://gitee.com/adamjiangwh/gof
如对本文有疑问, 点击进行留言回复!!
荐 厉害了!阿里P8架构师用4大技术文档带你深入解读爆火的中台战略
FlowableException: Error initialising dmn data model报错问题
网友评论