访问者模式(Visitor Pattern)

简介

访问者模式是一种将算法与对象结构分离的软件设计模式。

这个模式的基本想法如下:首先我们拥有一个由许多对象构成的对象结构,这些对象的类都拥有一个accept方法用来接受访问者对象;访问者是一个接口,它拥有一个visit方法,这个方法对访问到的对象结构中不同类型的元素作出不同的反应;在对象结构的一次访问过程中,我们遍历整个对象结构,对每一个元素都实施accept方法,在每一个元素的accept方法中回调访问者的visit方法,从而使访问者得以处理对象结构的每一个元素。我们可以针对对象结构设计不同的实在的访问者类来完成不同的操作。

访问者模式使得我们可以在传统的单分派语言(如Smalltalk、Java和C++)中模拟双分派技术。对于支持多分派的语言(如CLOS),访问者模式已经内置于语言特性之中了,从而不再重要。

  1. interface Visitor {
  2. void visit(Wheel wheel);
  3. void visit(Engine engine);
  4. void visit(Body body);
  5. void visit(Car car);
  6. }
  7. class Wheel {
  8. private String name;
  9. Wheel(String name) {
  10. this.name = name;
  11. }
  12. String getName() {
  13. return this.name;
  14. }
  15. void accept(Visitor visitor) {
  16. visitor.visit(this);
  17. }
  18. }
  19. class Engine {
  20. void accept(Visitor visitor) {
  21. visitor.visit(this);
  22. }
  23. }
  24. class Body {
  25. void accept(Visitor visitor) {
  26. visitor.visit(this);
  27. }
  28. }
  29. class Car {
  30. private Engine engine = new Engine();
  31. private Body body = new Body();
  32. private Wheel[] wheels
  33. = { new Wheel("front left"), new Wheel("front right"),
  34. new Wheel("back left") , new Wheel("back right") };
  35. void accept(Visitor visitor) {
  36. visitor.visit(this);
  37. engine.accept(visitor);
  38. body.accept(visitor);
  39. for (int i = 0; i < wheels.length; ++ i)
  40. wheels[i].accept(visitor);
  41. }
  42. }
  43. class PrintVisitor implements Visitor {
  44. public void visit(Wheel wheel) {
  45. System.out.println("Visiting " + wheel.getName()
  46. + " wheel");
  47. }
  48. public void visit(Engine engine) {
  49. System.out.println("Visiting engine");
  50. }
  51. public void visit(Body body) {
  52. System.out.println("Visiting body");
  53. }
  54. public void visit(Car car) {
  55. System.out.println("Visiting car");
  56. }
  57. }
  58. public class VisitorDemo {
  59. static public void main(String[] args) {
  60. Car car = new Car();
  61. Visitor visitor = new PrintVisitor();
  62. car.accept(visitor);
  63. }
  64. }

实例

男人和女人

‘人’类,是’男人’和’女人’类的抽象类

  1. abstract class person
  2. {
  3. protected string action;
  4. public string Action
  5. {
  6. get {return action;}
  7. set {action = value};
  8. }
  9. //得到结论或者反应
  10. public abstract void GetConclusion();
  11. }

“男人”类

  1. class Man: Person
  2. {
  3. //得到结论或反应
  4. public override void GetConclusion()
  5. {
  6. if (action == "成功")
  7. {
  8. Console.WriteLine("{0}{1}时,背后多半有一个伟大的女人",this.GetType().name,action);
  9. }
  10. else if (action == "失败")
  11. {
  12. Console.WriteLine("{0}{1}时,"蒙头喝酒,谁也不用劝",this.GetType().name,action);
  13. }
  14. else if (action == "恋爱")
  15. {
  16. Console.WriteLine("{0}{1}时,"凡事不懂也要装懂",this.GetType().name,action);
  17. }
  18. }
  19. }

“女人”类

  1. class Woman: Person
  2. {
  3. //得到结论或反应
  4. public override void GetConclusion()
  5. {
  6. if (action == "成功")
  7. {
  8. Console.WriteLine("{0}{1}时,背后多半有一个不成功的男人",this.GetType().name,action);
  9. }
  10. else if (action == "失败")
  11. {
  12. Console.WriteLine("{0}{1}时,"泪眼汪汪,谁也劝不了",this.GetType().name,action);
  13. }
  14. else if (action == "恋爱")
  15. {
  16. Console.WriteLine("{0}{1}时,"凡事懂也要装不懂",this.GetType().name,action);
  17. }
  18. }
  19. }

客户端代码

  1. static void Main(string[] args)
  2. {
  3. IList<Person> persons = new List<Person>();
  4. Person man1 = new Man();
  5. man1.Action = "成功";
  6. persons.add(man1);
  7. Person woman1 = new Woman();
  8. woman1.Action = "成功";
  9. persons.add(woman1);
  10. Person man2 = new Man();
  11. man2.Action = "失败";
  12. persons.add(man2);
  13. Person woman2 = new Woman();
  14. woman2.Action = "失败";
  15. persons.add(woman2);
  16. Person man3 = new Man();
  17. man3.Action = "恋爱";
  18. persons.add(man3);
  19. Person woman3 = new Woman();
  20. woman3.Action = "恋爱";
  21. persons.add(woman3);
  22. foreach (Person item in persons)
  23. {
  24. item.GetConclusion();
  25. }
  26. Console.Read();
  27. }

这个算是面向对象编程,但是’男人’和’女人’类当中的那些if….else….很是碍眼。而且如果我们增加一个’结婚状态’,怎么改?

用模式实现

‘状态’的抽象和’人’的抽象

  1. abstract class Action
  2. {
  3. //得到男人结论或反应
  4. public abstract void GetManConclusion(Man concreteElementA);
  5. public abstract void GetWomanConclusion(Woman concreteElementB);
  6. }
  7. abstract class Person
  8. {
  9. //接受
  10. public abstract void Accept(Action visitor);
  11. }

具体”状态”类

  1. //成功
  2. class Success: Action
  3. {
  4. public override void GetManConclusion(Man concreteElementA)
  5. {
  6. Console.WriteLine("{0}{1}时,背后多半有一个伟大的女人",this.GetType().name,action);
  7. }
  8. public abstract void GetWomanConclusion(Woman concreteElementB)
  9. {
  10. Console.WriteLine("{0}{1}时,背后多半有一个不成功的男人",this.GetType().name,action);
  11. }
  12. }
  13. //失败
  14. class Falling: Action
  15. {
  16. public override void GetManConclusion(Man concreteElementA)
  17. {
  18. Console.WriteLine("{0}{1}时,"蒙头喝酒,谁也不用劝",this.GetType().name,action);
  19. }
  20. public abstract void GetWomanConclusion(Woman concreteElementB)
  21. {
  22. Console.WriteLine("{0}{1}时,"泪眼汪汪,谁也劝不了",this.GetType().name,action);
  23. }
  24. }
  25. //恋爱
  26. class Amativeness: Action
  27. {
  28. public override void GetManConclusion(Man concreteElementA)
  29. {
  30. Console.WriteLine("{0}{1}时,"凡事不懂也要装懂",this.GetType().name,action);
  31. }
  32. public abstract void GetWomanConclusion(Woman concreteElementB)
  33. {
  34. Console.WriteLine("{0}{1}时,"凡事懂也要装不懂",this.GetType().name,action);
  35. }
  36. }

“男人”类和”女人”类

  1. //男人
  2. class Man : Person
  3. {
  4. public override void Accept(Action visitor)
  5. {
  6. //***
  7. visitor.GetManConclusion(this);
  8. }
  9. }
  10. //女人
  11. class Woman : Person
  12. {
  13. public override void Accept(Action visitor)
  14. {
  15. //***
  16. visitor.GetWomanConclusion(this);
  17. }
  18. }

这里需要讲下双派技术,首先客户端将具体状态作为参数传递给”男人”完成了一次分派,然后男人类调用方法”男人反应”,同时将自己作为参数传递进去。这便完成了第二次分派。双分派意味着得到执行的操作决定于请求的种类和两种接受者的类型。”接受”方法就是一个双分派的操作,它得到执行的操作不仅决定于’状态’类的具体状态,还决定于它访问的’人’的类别。

  1. //对象结构
  2. class ObjectStructure
  3. {
  4. private IList<Person>elements = new List<Person>();
  5. //增加
  6. public void Attach(Person element)
  7. {
  8. elements.Add(element);
  9. }
  10. //移除
  11. public void Detach(Person element)
  12. {
  13. elements.Remove(element);
  14. }
  15. //查看显示
  16. public void Display(Action visitor)
  17. {
  18. foreach(Person e in elements)
  19. {
  20. e.Accept(visitor);
  21. }
  22. }
  23. }

客户端代码

  1. static void Main(string[] args)
  2. {
  3. ObjectStructure o = new ObjectStructure();
  4. o.Attach(new Man());
  5. o.Attach(new Man());
  6. //成功时反应
  7. Success v1 = new Success();
  8. o.Display(v1);
  9. //失败时反应
  10. Success v1 = new Success();
  11. o.Display(v1);
  12. //恋爱时反应
  13. Success v1 = new Success();
  14. o.Display(v1);
  15. Console.Read();
  16. }

访问者适合于数据结构相对稳定的系统。它把数据结构和作用于结构之上的操作之间的耦合解脱开来,使得操作集合可以相对自由地y演化。

访问者模式的目的是要把处理从数据结构分离出来。如果这样的系统有比较稳定的数据结构,又有易于变化的算法的话,使用访问者模式就是比较适合的,因为访问者模式使得算法操作的增加变得容易。

那其实访问者模式的优点就是增加新的操作很容易,因为增加新的操作就意味着增加一个新的访问者,访问者模式将有关的行为集中到一个访问者对象中。

那访问者的缺点其实也就是使得增加新的数据结构变的困难了。

正如《设计模式》的作者GoF对访问者模式的描述:大多数情况下,你并需要使用访问者模式,但是当你一旦需要使用它时,那你就是真的需要它了。当然这只是针对真正的大牛而言。在现实情况下(至少是我所处的环境当中),很多人往往沉迷于设计模式,他们使用一种设计模式时,从来不去认真考虑所使用的模式是否适合这种场景,而往往只是想展示一下自己对面向对象设计的驾驭能力。编程时有这种心理,往往会发生滥用设计模式的情况。所以,在学习设计模式时,一定要理解模式的适用性。必须做到使用一种模式是因为了解它的优点,不使用一种模式是因为了解它的弊端;而不是使用一种模式是因为不了解它的弊端,不使用一种模式是因为不了解它的优点。