C#设计模式-访问者模式

1、 访问者(Vistor)模式算法

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

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

2、 访问者模式的结构dom

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

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

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

3、 访问者模式的实现spa

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

using System;
using System.Collections;

namespace VistorPattern
{
    // 抽象元素角色
    public abstract class Element
    {
        public abstract void Print();
    }

    // 具体元素A
    public class ElementA : Element
    {
        public override void Print()
        {
            Console.WriteLine("我是元素A");
        }
    }

    // 具体元素B
    public class ElementB : Element
    {
        public override void Print()
        {
            Console.WriteLine("我是元素B");
        }
    }

    // 对象结构
    public class ObjectStructure
    {
        private readonly ArrayList _elements = new ArrayList();

        public ArrayList Elements
        {
            get { return _elements; }
        }

        public ObjectStructure()
        {
            var ran = new Random();
            for (int i = 0; i < 6; i++)
            {
                int ranNum = ran.Next(10);
                if (ranNum > 5)
                {
                    _elements.Add(new ElementA());
                }
                else
                {
                    _elements.Add(new ElementB());
                }
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var objectStructure = new ObjectStructure();
            // 遍历对象结构中的对象集合,访问每一个元素的Print方法打印元素信息
            foreach (Element e in objectStructure.Elements)
            {
                e.Print();
            }

            Console.Read();
        }
    }
}

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

using System;
using System.Collections;

namespace VistorPattern
{
    // 抽象元素角色
    public abstract class Element
    {
        public abstract void Accept(IVistor vistor);
        public abstract void Print();
    }

    // 具体元素A
    public class ElementA : Element
    {
        public override void Accept(IVistor vistor)
        {
            // 调用访问者visit方法
            vistor.Visit(this);
        }
        public override void Print()
        {
            Console.WriteLine("我是元素A");
        }
    }

    // 具体元素B
    public class ElementB : Element
    {
        public override void Accept(IVistor vistor)
        {
            vistor.Visit(this);
        }
        public override void Print()
        {
            Console.WriteLine("我是元素B");
        }
    }

    // 抽象访问者
    public interface IVistor
    {
        void Visit(ElementA a);
        void Visit(ElementB b);
    }

    // 具体访问者
    public class ConcreteVistor : IVistor
    {
        // visit方法而是再去调用元素的Accept方法
        public void Visit(ElementA a)
        {
            a.Print();
        }
        public void Visit(ElementB b)
        {
            b.Print();
        }
    }

    // 对象结构
    public class ObjectStructure
    {
        private readonly ArrayList _elements = new ArrayList();

        public ArrayList Elements
        {
            get { return _elements; }
        }

        public ObjectStructure()
        {
            var ran = new Random();
            for (int i = 0; i < 6; i++)
            {
                int ranNum = ran.Next(10);
                if (ranNum > 5)
                {
                    _elements.Add(new ElementA());
                }
                else
                {
                    _elements.Add(new ElementB());
                }
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var objectStructure = new ObjectStructure();
            foreach (Element e in objectStructure.Elements)
            {
                // 每一个元素接受访问者访问
                e.Accept(new ConcreteVistor());
            }

            Console.Read();
        }
    }
}

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

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

每一个设计模式都有其应当使用的状况,那让咱们看看访问者模式具体应用场景。若是遇到如下场景,此时咱们能够考虑使用访问者模式。

  1. 若是系统有比较稳定的数据结构,而又有易于变化的算法时,此时能够考虑使用访问者模式。由于访问者模式使得算法操做的添加比较容易。
  2. 若是一组类中,存在着类似的操做,为了不出现大量重复的代码,能够考虑把重复的操做封装到访问者中。(固然也能够考虑使用抽象类了)
  3. 若是一个对象存在着一些与自己对象不相干,或关系比较弱的操做时,为了不操做污染这个对象,则能够考虑把这些操做封装到访问者对象中。

5、 访问者模式的优缺点

优势:

  1. 访问者模式使得添加新的操做变得容易。若是一些操做依赖于一个复杂的结构对象的话,那么通常而言,添加新的操做会变得很复杂。而使用访问者模式,增长新的操做就意味着添加一个新的访问者类。所以,使得添加新的操做变得容易。
  2. 访问者模式使得有关的行为操做集中到一个访问者对象中,而不是分散到一个个的元素类中。这点相似与"中介者模式"。
  3. 访问者模式能够访问属于不一样的等级结构的成员对象,而迭代只能访问属于同一个等级结构的成员对象。

缺点:

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

6、 总结

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

相关文章
相关标签/搜索