22.设计模式_访问者模式

一、引言

  在上一篇博文中分享了责任链模式,责任链模式主要应用在系统中的某些功能需要多个对象参与才能完成的场景。在这篇博文中,我将为大家分享我对访问者模式的理解。

二、访问者模式介绍

2.1 访问者模式的定义

  访问者模式是封装一些施加于某种数据结构之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构则可以保存不变。访问者模式适用于数据结构相对稳定的系统, 它把数据结构和作用于数据结构之上的操作之间的耦合度降低,使得操作集合可以相对自由地改变。

  数据结构的每一个节点都可以接受一个访问者的调用,此节点向访问者对象传入节点对象,而访问者对象则反过来执行节点对象的操作。这样的过程叫做“双重分派”。节点调用访问者,将它自己传入,访问者则将某算法针对此节点执行。

2.2 访问者模式的结构图

  从上面描述可知,访问者模式是用来封装某种数据结构中的方法。具体封装过程是:每个元素接受一个访问者的调用,每个元素的Accept方法接受访问者对象作为参数传入,访问者对象则反过来调用元素对象的操作。具体的访问者模式结构图如下所示。

  这里需要明确一点:访问者模式中具体访问者的数目和具体节点的数目没有任何关系。从访问者的结构图可以看出,访问者模式涉及以下几类角色。

  • 抽象访问者角色(Vistor):声明一个活多个访问操作,使得所有具体访问者必须实现的接口。
  • 具体访问者角色(ConcreteVistor):实现抽象访问者角色中所有声明的接口。
  • 抽象节点角色(Element):声明一个接受操作,接受一个访问者对象作为参数。
  • 具体节点角色(ConcreteElement):实现抽象元素所规定的接受操作。
  • 结构对象角色(ObjectStructure):节点的容器,可以包含多个不同类或接口的容器。

2.3 访问者模式的实现

  在讲诉访问者模式的实现时,我想先不用访问者模式的方式来实现某个场景。具体场景是——现在我想遍历每个元素对象,然后调用每个元素对象的Print方法来打印该元素对象的信息。如果此时不采用访问者模式的话,实现这个场景再简单不过了,具体实现代码如下所示:

 1 namespace DonotUsevistorPattern
 2 {
 3     // 抽象元素角色
 4     public abstract class Element
 5     {
 6         public abstract void Print();
 7     }
 8
 9     // 具体元素A
10     public class ElementA : Element
11     {
12         public override void Print()
13         {
14             Console.WriteLine("我是元素A");
15         }
16     }
17
18     // 具体元素B
19     public class ElementB : Element
20     {
21         public override void Print()
22         {
23             Console.WriteLine("我是元素B");
24         }
25     }
26
27     // 对象结构
28     public class ObjectStructure
29     {
30         private ArrayList elements = new ArrayList();
31
32         public ArrayList Elements
33         {
34             get { return elements; }
35         }
36
37         public ObjectStructure()
38         {
39             Random ran = new Random();
40             for (int i = 0; i < 6; i++)
41             {
42                 int ranNum = ran.Next(10);
43                 if (ranNum > 5)
44                 {
45                     elements.Add(new ElementA());
46                 }
47                 else
48                 {
49                     elements.Add(new ElementB());
50                 }
51             }
52         }
53     }
54
55     class Program
56     {
57         static void Main(string[] args)
58         {
59             ObjectStructure objectStructure = new ObjectStructure();
60             // 遍历对象结构中的对象集合,访问每个元素的Print方法打印元素信息
61             foreach (Element e in objectStructure.Elements)
62             {
63                 e.Print();
64             }
65
66             Console.Read();
67         }
68     }
69 }

  上面代码很准确了解决了我们刚才提出的场景,但是需求在时刻变化的,如果此时,我除了想打印元素的信息外,还想打印出元素被访问的时间,此时我们就不得不去修改每个元素的Print方法,再加入相对应的输入访问时间的输出信息。这样的设计显然不符合“开-闭”原则,即某个方法操作的改变,会使得必须去更改每个元素类。既然,这里变化的点是操作的改变,而每个元素的数据结构是不变的。所以此时就思考——能不能把操作于元素的操作和元素本身的数据结构分开呢?解开这两者的耦合度,这样如果是操作发现变化时,就不需要去更改元素本身了,但是如果是元素数据结构发现变化,例如,添加了某个字段,这样就不得不去修改元素类了。此时,我们可以使用访问者模式来解决这个问题,即把作用于具体元素的操作由访问者对象来调用。具体的实现代码如下所示:

  1 namespace VistorPattern
  2 {
  3     // 抽象元素角色
  4     public abstract class Element
  5     {
  6         public abstract void Accept(IVistor vistor);
  7         public abstract void Print();
  8     }
  9
 10     // 具体元素A
 11     public class ElementA :Element
 12     {
 13         public override void Accept(IVistor vistor)
 14         {
 15             // 调用访问者visit方法
 16             vistor.Visit(this);
 17         }
 18         public override void Print()
 19         {
 20             Console.WriteLine("我是元素A");
 21         }
 22     }
 23
 24     // 具体元素B
 25     public class ElementB :Element
 26     {
 27         public override void Accept(IVistor vistor)
 28         {
 29             vistor.Visit(this);
 30         }
 31         public override void Print()
 32         {
 33             Console.WriteLine("我是元素B");
 34         }
 35     }
 36
 37     // 抽象访问者
 38     public interface IVistor
 39     {
 40         void Visit(ElementA a);
 41         void Visit(ElementB b);
 42     }
 43
 44     // 具体访问者
 45     public class ConcreteVistor :IVistor
 46     {
 47         // visit方法而是再去调用元素的Accept方法
 48         public void Visit(ElementA a)
 49         {
 50             a.Print();
 51         }
 52         public void Visit(ElementB b)
 53         {
 54             b.Print();
 55         }
 56     }
 57
 58     // 对象结构
 59     public class ObjectStructure
 60     {
 61         private ArrayList elements = new ArrayList();
 62
 63         public ArrayList Elements
 64         {
 65             get { return elements; }
 66         }
 67
 68         public ObjectStructure()
 69         {
 70             Random ran = new Random();
 71             for (int i = 0; i < 6; i++)
 72             {
 73                 int ranNum = ran.Next(10);
 74                 if (ranNum > 5)
 75                 {
 76                     elements.Add(new ElementA());
 77                 }
 78                 else
 79                 {
 80                     elements.Add(new ElementB());
 81                 }
 82             }
 83         }
 84     }
 85
 86     class Program
 87     {
 88         static void Main(string[] args)
 89         {
 90             ObjectStructure objectStructure = new ObjectStructure();
 91             foreach (Element e in objectStructure.Elements)
 92             {
 93                 // 每个元素接受访问者访问
 94                 e.Accept(new ConcreteVistor());
 95             }
 96
 97             Console.Read();
 98         }
 99     }
100 }

  从上面代码可知,使用访问者模式实现上面场景后,元素Print方法的访问封装到了访问者对象中了(我觉得可以把Print方法封装到具体访问者对象中。),此时客户端与元素的Print方法就隔离开了。此时,如果需要添加打印访问时间的需求时,此时只需要再添加一个具体的访问者类即可。此时就不需要去修改元素中的Print()方法了。

三、访问者模式的应用场景

  每个设计模式都有其应当使用的情况,那让我们看看访问者模式具体应用场景。如果遇到以下场景,此时我们可以考虑使用访问者模式。

  • 如果系统有比较稳定的数据结构,而又有易于变化的算法时,此时可以考虑使用访问者模式。因为访问者模式使得算法操作的添加比较容易。
  • 如果一组类中,存在着相似的操作,为了避免出现大量重复的代码,可以考虑把重复的操作封装到访问者中。(当然也可以考虑使用抽象类了)
  • 如果一个对象存在着一些与本身对象不相干,或关系比较弱的操作时,为了避免操作污染这个对象,则可以考虑把这些操作封装到访问者对象中。

四、访问者模式的优缺点

  访问者模式具有以下优点:

  • 访问者模式使得添加新的操作变得容易。如果一些操作依赖于一个复杂的结构对象的话,那么一般而言,添加新的操作会变得很复杂。而使用访问者模式,增加新的操作就意味着添加一个新的访问者类。因此,使得添加新的操作变得容易。
  • 访问者模式使得有关的行为操作集中到一个访问者对象中,而不是分散到一个个的元素类中。这点类似与"中介者模式"。
  • 访问者模式可以访问属于不同的等级结构的成员对象,而迭代只能访问属于同一个等级结构的成员对象。

  访问者模式也有如下的缺点:

  • 增加新的元素类变得困难。每增加一个新的元素意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中添加相应的具体操作。

五、总结

  访问者模式是用来封装一些施加于某种数据结构之上的操作。它使得可以在不改变元素本身的前提下增加作用于这些元素的新操作,访问者模式的目的是把操作从数据结构中分离出来。

时间: 2024-12-28 21:40:35

22.设计模式_访问者模式的相关文章

OO设计模式_访问者模式

Motivation: 在软件构建过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为(方法),如果直接在基类中做这样的更改,将会给子类带来很繁重的变更负担,甚至破坏原有的设计. 如果在不更改类层次结构的前提下,在运行时根据需要透明地为类层次结构上的各个类动态添加新的操作,从而避免上述问题? Intent: 表示一个作用于某个对象结构中各个元素的操作.它可以在不改变各元素的类的前提下定义作用于这些元素的新的操作. Main point: Visitor模式通过所谓双重分发(double

设计模式_访问者模式

Visitor Pattern Repressent an operation to be performed  on the elements of an object structure.Visitor lets you define a new operation without changing the classees of the elements on which it operates.(封装一些作用于某种数据结构中的各种元素的操作,它可以在不改变数据结构的前提下定义作用于这些元

大话设计模式_备忘录模式(Java代码)

备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态.这样以后就可将该对象恢复到原先保存的状态. 简单描述:一个Memento类,代表Originator中要备份的属性.Originator负责生成备份和还原备份,CareTaker负责存储备份 大话设计模式中的截图: 例子代码: Memento类: 1 package com.longsheng.memento; 2 3 public class Memento { 4 5 private String sta

大话设计模式_解释器模式(Java代码)

解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子. 简单描述:一个AbstractExpression类,多个子类,存在一个Interpret方法,转义Context对象的信息.客户端根据信息实例化不同的Expression类,并调用其转义方法(这个过程可以使用简单工厂+反射进行) 大话设计模式中的截图: 代码例子: 假设HTML代码解释器: (1)第一类标签<HTML>(开始)/<HEAD>(头信息)/<BODY&g

大话设计模式_原型模式(Java代码)

原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象 简单描述:即通过实现接口Cloneable重写方法clone(),使得创建新的拷贝对象不需要一个成员一个成员的重新复制,而且可以提高创建对象的效率 Java中要想实现拷贝使用clone()方法,类必须实现Cloneable接口,并且重写Object类中的clone()方法,调用父类的clone()方法即可实现浅复制 代码如下: WorkExperience类: 1 package com.longsheng.prototy

大话设计模式_状态模式(Java代码)

状态模式:当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类. 简单描述:一个Context类(存有一个抽象状态State引用),调用状态类的方法.State的具体类方法中会判断Context类的状态(如时间),满足一个状态则执行相应动作,否则把Context的State引用指向下一个状态类,由下一个状态类决定相应行为 大话设计模式中的截图: 例子代码: Work类(Context): 1 package com.longsheng.state; 2 3 public cla

大话设计模式_建造者模式(Java代码)

建造者模式:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示 简单描述:1个产品类(可有可无,关键是建造流程),1个抽象建造步骤类,多个具体建造子类(不同的类建造不同的产品),1个指挥者(用于规定建造流程),客户端指定需要建造的具体类型,由指挥者建造好之后,建造者子类返回对应产品给客户 大话设计模式中的截图: 例子代码: Product类: 1 package com.longsheng.builder; 2 3 public class Product { 4 5 pr

大话设计模式_策略模式(Java代码)

策略模式:定义算法家族,分别封装,让它们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户 简单描述:一个父类,多个子类实现具体方法.一个Context类持有父类的引用(使用子类实例化此引用),客户端代码只需要与此Context类交互即可 大话设计模式中的截图: 例子代码: 策略类: 1 package com.longsheng.strategy; 2 3 public abstract class Strategy { 4 5 public abstract double getR

设计模式之访问者模式(Visitor)摘录

23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式抽象了实例化过程,它们帮助一个系统独立于如何创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化委托给另一个对象.创建型模式有两个不断出现的主旋律.第一,它们都将关于该系统使用哪些具体的类的信息封装起来.第二,它们隐藏了这些类的实例是如何被创建和放在一起的.整个系统关于这些对象所知道的是由抽象类所定义的接口.因此,创建型模式在什么被创建,谁创建它,它是怎样被创建的,以