zoukankan      html  css  js  c++  java
  • C#设计模式之二十一访问者模式(Visitor Pattern)【行为型】

    一、引言

       今天我们开始讲“行为型”设计模式的第九个模式,该模式是【访问者模式】,英文名称是:Visitor Pattern。如果按老规矩,先从名称上来看看这个模式,我根本不能获得任何对理解该模式有用的信息,而且这个模式在我们的编码生活中使用的并不是很多。该模式的意图定义很抽象,第一次看了这个定义其实和没看没有什么区别,一头雾水,为了让大家更好的理解该模式的初衷,我们举个例子来说明模式。比如:当我们为了解决一个新的软件需求的时候,经过多个日以继夜的努力,最终通过一个完美(自己认为的)的软件设计解决了客户提出的新的需求,而且这个设计有完美的类层次结构,并且是符合OO的设计原则的,我们很开心,对自己设计的东西很有成就感。又过了一段时间,客户突然又有了一个新的需求,需要为现有的类层次结构里面的类增加一个新的操作(其实就是一个方法),怎么办?好办,在面向OO设计模式中有一个模式就是为了解决这个问题的,那就是“访问者模式”,可以为现有的类层次结构中的类轻松增加新的操作,我们继续吧,好好的了解一下该模式。

    二、访问者模式的详细介绍

    2.1、动机(Motivate)

       在软件构建过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为(方法),如果直接在基类中做这样的更改,将会给子类带来很繁重的变更负担,甚至破坏原有设计。如何在不更改类层次结构的前提下,在运行时根据需要透明地为类层次结构上的各个类动态添加新的操作,从而避免上述问题?

    2.2、意图(Intent)

       表示一个作用于某对象结构中的各个元素的操作。它可以在不改变各元素的类的前提下定义作用于这些元素的新的操作。                                      ——《设计模式》GoF

    2.3、结构图(Structure)

        

    2.4、模式的组成
        
        可以看出,在访问者模式的结构图有以下角色:

        (1)、抽象访问者角色(Vistor): 声明一个包括多个访问操作,多个操作针对多个具体节点角色(可以说有多少个具体节点角色就有多少访问操作),使得所有具体访问者必须实现的接口。

        (2)、具体访问者角色(ConcreteVistor):实现抽象访问者角色中所有声明的接口,也可以说是实现对每个具体节点角色的新的操作。

        (3)、抽象节点角色(Element):声明一个接受操作,接受一个访问者对象作为参数,如果有其他参数,可以在这个“接受操作”里在定义相关的参数。

        (4)、具体节点角色(ConcreteElement):实现抽象元素所规定的接受操作。

        (5)、结构对象角色(ObjectStructure):节点的容器,可以包含多个不同类或接口的容器。

    2.5、访问者模式的代码实现

        访问者这个模式在我们现实的编码生活中使用的并不是很多,我就直接贴代码,让大家看代码的结构吧。今天给大家两个代码实例,自己慢慢体会访问者吧。实现代码如下:

      1 namespace Vistor
      2 {
      3     //抽象图形定义---相当于“抽象节点角色”Element
      4     public abstract class Shape
      5     {
      6         //画图形
      7         public abstract void Draw();
      8         //外界注入具体访问者
      9         public abstract void Accept(ShapeVisitor visitor);
     10     }
     11 
     12     //抽象访问者 Visitor
     13     public abstract class ShapeVisitor
     14     {
     15         public abstract void Visit(Rectangle shape);
     16 
     17         public abstract void Visit(Circle shape);
     18 
     19         public abstract void Visit(Line shape);
     20 
     21         //这里有一点要说:Visit方法的参数可以写成Shape吗?就是这样 Visit(Shape shape),当然可以,但是ShapeVisitor子类Visit方法就需要判断当前的Shape是什么类型,是Rectangle类型,是Circle类型,或者是Line类型。
     22     }
     23 
     24     //具体访问者 ConcreteVisitor
     25     public sealed class CustomVisitor : ShapeVisitor
     26     {
     27         //针对Rectangle对象
     28         public override void Visit(Rectangle shape)
     29         {
     30             Console.WriteLine("针对Rectangle新的操作!");
     31         }
     32         //针对Circle对象
     33         public override void Visit(Circle shape)
     34         {
     35             Console.WriteLine("针对Circle新的操作!");
     36         }
     37         //针对Line对象
     38         public override void Visit(Line shape)
     39         {
     40             Console.WriteLine("针对Line新的操作!");
     41         }
     42     }
     43 
     44     //矩形----相当于“具体节点角色” ConcreteElement
     45     public sealed class Rectangle : Shape
     46     {
     47         public override void Draw()
     48         {
     49             Console.WriteLine("矩形我已经画好!");
     50         }
     51 
     52         public override void Accept(ShapeVisitor visitor)
     53         {
     54             visitor.Visit(this);
     55         }
     56     }
     57 
     58     //圆形---相当于“具体节点角色”ConcreteElement
     59     public sealed class Circle : Shape
     60     {
     61         public override void Draw()
     62         {
     63             Console.WriteLine("圆形我已经画好!");
     64         }
     65 
     66         public override void Accept(ShapeVisitor visitor)
     67         {
     68             visitor.Visit(this);
     69         }
     70     }
     71 
     72     //直线---相当于“具体节点角色” ConcreteElement
     73     public sealed class Line : Shape
     74     {
     75         public override void Draw()
     76         {
     77             Console.WriteLine("直线我已经画好!");
     78         }
     79 
     80         public override void Accept(ShapeVisitor visitor)
     81         {
     82             visitor.Visit(this);
     83         }
     84     }
     85 
     86     //结构对象角色
     87     internal class AppStructure
     88     {
     89         private ShapeVisitor _visitor;
     90 
     91         public AppStructure(ShapeVisitor visitor)
     92         {
     93             this._visitor = visitor;
     94         }
     95 
     96         public void Process(Shape shape)
     97         {
     98             shape.Accept(_visitor);
     99         }
    100     }
    101 
    102     class Program
    103     {
    104         static void Main(string[] args)
    105         {
    106             //如果想执行新增加的操作
    107             ShapeVisitor visitor = new CustomVisitor();
    108             AppStructure app = new AppStructure(visitor);
    109 
    110             Shape shape = new Rectangle();
    111             shape.Draw();//执行自己的操作
    112             app.Process(shape);//执行新的操作
    113 
    114 
    115             shape = new Circle();
    116             shape.Draw();//执行自己的操作
    117             app.Process(shape);//执行新的操作
    118 
    119 
    120             shape = new Line();
    121             shape.Draw();//执行自己的操作
    122             app.Process(shape);//执行新的操作
    123 
    124 
    125             Console.ReadLine();
    126         }
    127     }
    128 }

    这是访问者模式第二种代码实例:

      1 namespace Visitor
      2 {
      3     //抽象访问者角色 Visitor
      4     public abstract class Visitor
      5     {
      6         public abstract void PutTelevision(Television tv);
      7 
      8         public abstract void PutComputer(Computer comp);
      9     }
     10 
     11     //具体访问者角色 ConcreteVisitor
     12     public sealed class SizeVisitor : Visitor
     13     {
     14         public override void PutTelevision(Television tv)
     15         {
     16             Console.WriteLine("按商品大小{0}排放", tv.Size);
     17         }
     18 
     19         public override void PutComputer(Computer comp)
     20         {
     21             Console.WriteLine("按商品大小{0}排放", comp.Size);
     22         }
     23     }
     24 
     25     //具体访问者角色 ConcreteVisitor
     26     public sealed class StateVisitor : Visitor
     27     {
     28         public override void PutTelevision(Television tv)
     29         {
     30             Console.WriteLine("按商品新旧值{0}排放", tv.State);
     31         }
     32 
     33         public override void PutComputer(Computer comp)
     34         {
     35             Console.WriteLine("按商品新旧值{0}排放", comp.State);
     36         }
     37     }
     38 
     39     //抽象节点角色 Element
     40     public abstract class Goods
     41     {
     42         public abstract void Operate(Visitor visitor);
     43 
     44         private int nSize;
     45         public int Size
     46         {
     47             get { return nSize; }
     48             set { nSize = value; }
     49         }
     50 
     51         private int nState;
     52         public int State
     53         {
     54             get { return nState; }
     55             set { nState = value; }
     56         }
     57     }
     58 
     59     //具体节点角色 ConcreteElement
     60     public sealed class Television : Goods
     61     {
     62         public override void Operate(Visitor visitor)
     63         {
     64             visitor.PutTelevision(this);
     65         }
     66     }
     67 
     68     //具体节点角色 ConcreteElement
     69     public sealed class Computer : Goods
     70     {
     71         public override void Operate(Visitor visitor)
     72         {
     73             visitor.PutComputer(this);
     74         }
     75     }
     76 
     77     //结构对象角色
     78     public sealed class StoragePlatform
     79     {
     80         private IList<Goods> list = new List<Goods>();
     81 
     82         public void Attach(Goods element)
     83         {
     84             list.Add(element);
     85         }
     86 
     87         public void Detach(Goods element)
     88         {
     89             list.Remove(element);
     90         }
     91 
     92         public void Operate(Visitor visitor)
     93         {
     94             foreach (Goods g in list)
     95             {
     96                 g.Operate(visitor);
     97             }
     98         }
     99     }
    100 
    101     class Program
    102     {
    103         static void Main(string[] args)
    104         {
    105             StoragePlatform platform = new StoragePlatform();
    106             platform.Attach(new Television());
    107             platform.Attach(new Computer());
    108 
    109             SizeVisitor sizeVisitor = new SizeVisitor();
    110             StateVisitor stateVisitor = new StateVisitor();
    111 
    112             platform.Operate(sizeVisitor);
    113             platform.Operate(stateVisitor);
    114 
    115             Console.Read();
    116         }
    117     }
    118 }


    三、访问者模式的实现要点:

        Visitor模式通过所谓双重分发(double dispatch)来实现在不更改Element类层次结构的前提下,在运行时透明地为类层次结构上的各个类动态添加新的操作。所谓双重分发即Visitor模式中间包括了两个多态分发(注意其中的多态机制):第一个为accept方法的多态辨析;第二个为visit方法的多态辨析。

      设计模式其实是一种堵漏洞的方式,但是没有一种设计模式能够堵完所有的漏洞,即使是组合各种设计模式也是一样。每个设计模式都有漏洞,都有它们解决不了的情况或者变化。每一种设计模式都假定了某种变化,也假定了某种不变化。Visitor模式假定的就是操作变化,而Element类层次结构稳定。

        (1)、访问者模式的主要优点有:

            1】、访问者模式使得添加新的操作变得容易。如果一些操作依赖于一个复杂的结构对象的话,那么一般而言,添加新的操作会变得很复杂。而使用访问者模式,增加新的操作就意味着添加一个新的访问者类。因此,使得添加新的操作变得容易。

            2】、访问者模式使得有关的行为操作集中到一个访问者对象中,而不是分散到一个个的元素类中。这点类似与”中介者模式”。

            3】、访问者模式可以访问属于不同的等级结构的成员对象,而迭代只能访问属于同一个等级结构的成员对象。

     (2)、访问者模式的主要缺点有:

            1】、增加新的元素类变得困难。每增加一个新的元素意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中添加相应的具体操作。具体来说,Visitor模式的最大缺点在于扩展类层次结构(增添新的Element子类),会导致Visitor类的改变。因此Visitor模式适用于“Element类层次结构稳定,而其中的操作却经常面临频繁改动”。

        (3)、在下面的情况下可以考虑使用访问者模式:

            1】、如果系统有比较稳定的数据结构,而又有易于变化的算法时,此时可以考虑使用访问者模式。因为访问者模式使得算法操作的添加比较容易。

            2】、如果一组类中,存在着相似的操作,为了避免出现大量重复的代码,可以考虑把重复的操作封装到访问者中。(当然也可以考虑使用抽象类了)

            3】、如果一个对象存在着一些与本身对象不相干,或关系比较弱的操作时,为了避免操作污染这个对象,则可以考虑把这些操作封装到访问者对象中。


    四、.NET 访问者模式的实现

         在现在的Net框架里面,如果要想给现有的类增加新的方法,有了新的方式,那就是“扩展方法”,使用起来和实例方法是一样一样的,而且在Net框架里面,微软自己也写了很多的扩展方法给我们使用。我目前还没有学习到Net的框架类库里面有“访问者模式”实现,看来自己还需努力,革命尚未成功啊。

    五、总结

        访问者模式写完了,这个模式刚开始理解起来还是挺麻烦的,但是,如果我们多看几个实例代码,完全掌握也不是问题。随着C#语言的发展,设计模式里面的很多东西,我们可以通过C#语言的一些特性做更好的替代。我们写设计模式刚开始要慢慢来,一步一步的照猫画虎的来写代码,等我们熟练掌握了模式的核心意思,我们就要写符合C#风格和特性的模式代码了,或者说我们要用C#来写设计模式了,写出来的代码会更棒。

  • 相关阅读:
    位运算
    LeetCode(230):二叉树中的第K小元素
    LeetCode(69):二分法求平方根
    TCP如何保证传输可靠性
    2种方法(递归+BFS)求二叉树的最小/最大深度
    自动生成Mapper文件(基于Mybatis Maven插件)
    Git的使用
    Java关键字及其作用详解
    Vagrant安装Centos/7
    java servlet 几种页面跳转的方法及传值
  • 原文地址:https://www.cnblogs.com/PatrickLiu/p/8135083.html
Copyright © 2011-2022 走看看