Design Pattern - 访问者模式

访问者模式

访问者模式(Visitor), 表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

这个模式相对比较复杂, 而又很少能被用上, 拿GOF作者的话'大多数时候你并不需要访问者模式, 但当你一旦需要它, 那就是真正的需要'

访问者模式的本质就是解数据结构和结构上的操作之间的耦合, 使操作集合可以自由的演化. 
看上去很美好, 不过有个比较苛刻的条件, 就是数据结构的类层次是稳定的, 不变的, 才可以使用. 
如下图, Element的子类只有ConcreteElement1和ConcreteElement2, 不会变化, 举个比较典型的例子, 人的性别, 只有男,女, 不会变的很稳定, 这样就可以使用访问者模式. 所以能满足这种条件的很少, 于是这个模式很少被用到.

场景, 为数据结构添加新的操作newfunc, 即Element添加新的成员函数newfunc() 
正规的做法就是, 修改Element, ConcreteElement1, ConcreteElement2来添加newfunc. 这样做违反开闭原则, 而且当这个数据结构的操作变化频繁的时候, 就很麻烦了.

这个模式的做法, 既然操作频繁变化, 就把操作独立出来, 对于每个操作生成一个Visitor子类. 对于一个操作而言, 对ConcreteElement1, ConcreteElement2的具体实现是不同的, 所以在ConcreteVisitor1类中, 要分别为ConcreteElement1, ConcreteElement2定义不同的操作函数. 
这儿就解释了为什么, Element的类层次必须稳定, 如果这儿增加一个ConcreteElement3, 那么在所有的Visitor中都必须添加相应的处理函数, 这就回到了上面的问题. 所以只有Element的类层次不变, 我们这样做才又意义. 
这个模式的好处就是, 在增加操作时, 不需要改变数据结构类, 只需要定义一个新的操作子类即可, 符合开闭原则.

public abstract class Visitor //把操作从数据结构中抽象出来
{
    public abstract void VisitConcreteElementA(ConcreteElementA concreteElementA);

    public abstract void VisitConcreteElementB(ConcreteElementB concreteElementB);
}

public class ConcreteVisitor1 : Visitor //新增加操作, 只需要添加Visitor子类
{
    public override void VisitConcreteElementA(ConcreteElementA concreteElementA)
    {
        //新操作对A的实现
    }

    public override void VisitConcreteElementB(ConcreteElementB concreteElementB)
    {
        //新操作对B的实现
    }

}

public class ConcreteElementA : Element
{
    //所谓的双分派技术, 即先把操作类作为参数传入, 再把this作为参数传给操作类
    //把具体操作细节给屏蔽了, 无论什么操作, 代码都不用变
    public override void Accept(Visitor visitor)
    {
        visitor.VisitConcreteElementA(this);
    }

}

//高层枚举类, 用来遍历执行Visitor操作
public class ObjectStructure
{
    IList<Element> elements=new List<Element>();

    public void Add(Element e)
    {
        elements.Add(e);
    }

    public void Remove(Element e)
    {
        elements.Remove(e);
    }

    public void Accept(Visitor visitor)
    {
        foreach (Element e in elements)
        {
            e.Accept(visitor);
        }
    }
}

//客户代码
ObjectStructure os = new ObjectStructure();
os.Add(new ConcreteElementA());
os.Add(new ConcreteElementB());

ConcreteVisitor1 cv1 = new ConcreteVisitor1();
ConcreteVisitor2 cv2 = new ConcreteVisitor2();

//对所有的element分别执行cv1和cv2两种操作
os.Accept(cv1);
os.Accept(cv2);

 

其实访问者模式要解决的表达式问题, 参考Protocol and DataType

对于表达式问题的两个子问题,

1. 将已存在的方法扩展到新的类型, 比较容易实现, 通过继承可以实现

2. 为已存在的类型扩展新的方法,

对于这个问题, 面向对象是比较难于解决的和比较麻烦, 对于一般情况, 需要去每个类里面去添加该方法...

可以说, 访问者模式就是用于解决这个问题的, 当然是有代价的, 代价就是不支持1, 理由上面已经讲了, 除非这种操作对于所有的类的逻辑都是一样的, 但这种情况不太常见

所以, 面向对象无法完美的解决表达式问题, 就算采取访问者模式解决了子问题2, 但是代价就是牺牲了1

而可以看到, clojure就可以比较完美的解决这个问题

本文章摘自博客园,原文发布日期: 2013-08-13

时间: 2024-09-23 07:04:05

Design Pattern - 访问者模式的相关文章

Design Pattern: Prototype 模式

  学习是分享和合作式的! 转载请注明出处:http://blog.csdn.net/wdzxl198/article/details/9271773: 文章摘自: http://www.riabook.cn/doc/designpattern/: 您从图书馆的期刊从发现了几篇您感兴趣的文章,由于这是图书馆的书,您不可以直接在书中作记号或写字,所以您将当中您所感兴趣的几个主题影印出来,这下子您就可在影印的文章上画记重点. Prototype模式的作用有些类似上面的描述,您在父类别中定义一个clo

Design Pattern: Builder 模式

  学习是分享和合作式的! 转载请注明出处:http://blog.csdn.net/wdzxl198/article/details/9248365:  文章摘自: http://www.riabook.cn/doc/designpattern/:  您想要建立一个迷宫产生程式,迷宫使用二维阵列来定义,0表示道路,1表示墙,2表示宝物,根据所定义的二维迷宫阵列,您想要程式自动产生各种不同材质的迷宫,例如砖墙迷宫,钻石迷宫等等. 您可以在程式中定义两个角色,一个是指导迷宫建立的Director角

Design Pattern: Proxy 模式

学习是分享和合作式的! 转载请注明出处:http://blog.csdn.net/wdzxl198/article/details/10472999: 文章摘自: http://www.riabook.cn/doc/designpattern/: 在 Gof 的书中对Proxy模式的目的给定为:为其它的物件提供一种代理,以控制对这个物件的访问.由这句话所延伸出来的意思是,根据您的目的不同,您的代理物件将负有不同的责任,因为产生多种不同的代理情况. 根据不同的代理目的,而有不同的代理情况,在Gof

Design Pattern: Adapter 模式 - Object Adapter

您的电脑是个旧电脑,新的滑鼠都在使用USB接口了,而您的电脑上并没有USB,而只有一个PS2接口,这时您可以使用一个USB转PS2的接头作为转换,这样您的电脑就可以使用新滑鼠了(当然您也可以使用USB扩充卡,意思是相同的).  类似的概念,有时候您想在原来的程式中加入一个外部元件,例如一个类别,但是这个类别与您目前所设计的程式在介面上并不一致,为了让这个外部类与原程式的介面一致,您必须使用一个类别作为中介来配接它们,这时您可以采用Adapter模式.  举个例子来说,在Java 1.0中有个En

Design Pattern: Singleton 模式

一句话概括:保证一个类仅有一个实例,并提供一个访问它的全局访问点. Singleton的英文意义是独身,也就是只有一个人,应用在物件导向语言上,通常翻译作单例:单一个实例(Instance).  很多时候,您会需要Singleton模式,例如印表机管理,您希望程式中只能有一个Print Spooler,以避免两个列印动作同时输入至印表机中:例如资料库管理,因为建立连接(Connection)物件会耗用资源,您希望程式中只能有一个 连接物件,所有其它的程式都透过这个物件来连接资料库,以避免连接物件

Design Pattern: Adapter 模式 - Class Adapter

Adapter模式的另一种作法是Class Adapter模式,在这个模式下,Adapter直接继承Adaptee(要引进的新类别),以拥有当中的成员及方法,在C++中的话可以这么作: C++中可以多重继承,但在Java中不行,所以在Java中若要采用Class Adapter,必须作点修改,一方面继承Adaptee,一方面实作Target的介面: 代码的实现是这样的: public class Adapter extends Adaptee implements Target {      /

Design Pattern: Flyweight 模式

学习是分享和合作式的! 转载请注明出处:http://blog.csdn.net/wdzxl198/article/details/10472999: 文章摘自: http://www.riabook.cn/doc/designpattern/: 在 Gof 的书中指出,Flyweight的目的在于运用共享技术,使得一些细粒度的物件可以共享. Flyweight在牛津字典中的解释是"boxer of the lightest class".意思是特轻量级拳击手?其实应该是取"

Design Pattern: Strategy 模式

  学习是分享和合作式的! 转载请注明出处:http://blog.csdn.net/wdzxl198/article/details/9306775: 文章摘自: http://www.riabook.cn/doc/designpattern/: 考虑您要设计一个更换各种符号的工具类TextCharChange,您是否会采用这样的方式: 1: public void replace() { 2: switch(getChangeType()) { 3: case RN_TYPE: 4: rep

Design Pattern: Composite 模式

  学习是分享和合作式的! 转载请注明出处:http://blog.csdn.net/wdzxl198/article/details/9417163: 文章摘自: http://www.riabook.cn/doc/designpattern/: 如果以绘图为例的话,一个文字是一个绘图元件,一个线段是一个绘图元件,而一个长方形也是一个绘图元件,这些绘图元件可以组成一个图片,如果将这个图片也 视作一个绘图元件,则这么递回绘图下去,就可以组合成一个较大的.复杂的图形元件,这样的目的可以使用Comp