通俗易懂设计模式解析——访问者模式

  • 2019 年 10 月 19 日
  • 笔记

前言

  今天我们看的是访问者模式【Visitor Pattern 】,我们理解访问者模式这个名称可能会有利于我们理解其核心代码块。我们看这么个例子:我去朋友家做客,那么朋友属于主人,我则属于访问者。这时刚好朋友在炒菜,却没得酱油了。如果朋友下去买酱油将会很麻烦而且会影响炒菜。这时就到我这个访问者出马了。一溜烟的出去打着酱油就回来了。简单理解的来说就是,访问者在主人原来的基础上帮助主人去完成主人不方便或者完不成的东西。

访问者模式介绍

一、来由

  在软件系统开发中,我们经常会遇见一些层次结构完好的代码因为需求的更改而更改。你说这个时候我更改其基类吧,所有子类都要更改、这是一件很麻烦的事情。那么我能不能在不修改器层次结构完整的前提下完成新的需求的更改呢?

二、意图

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

三、案例图

四、访问者模式代码示例

看上面案例图可以发现访问者模式包含以下部分:

结构对象:节点的容器,包含多个类或者接口
抽象节点:声明一个接收操作,接收访问者对象作为参数,声明处理接口,处理节点数据

具体节点:实现抽象节点的接收操作和处理操作

抽象访问者:声明一个或多个访问操作,使得所有具体访问者都必须实现

具体访问者:实现抽象访问者声明的接口

我们看这么一个需求,我们需要计算图形的面积并输出、图形包括长方形圆形。我们一起看看代码实现吧:

  首先我们看不采用访问者模式实现:

namespace Visitor_Pattern  {      class VisitorPattern      {      }        /// <summary>      /// 抽象节点类      /// </summary>      public abstract class Element      {         public abstract void CalculatedArea();      }        /// <summary>      /// 长方形计算面积输出      /// </summary>      public class Rectangle : Element      {          public double _long;          public double _wide;            public  Rectangle(double Long, double Wide)          {              this._long = Long;              this._wide = Wide;          }          public override void CalculatedArea()          {              Console.WriteLine($"长方形面积是:{_long*_wide}");          }      }        /// <summary>      /// 圆形计算面积输出      /// </summary>      public class Circular : Element      {          public double _r;            public Circular(double r)          {              this._r = r;          }          public override void CalculatedArea()          {              Console.WriteLine($"圆形面积是:{Math.PI * _r*_r}");          }      }        /// <summary>      /// 结构对象      /// </summary>      public class Graphical      {          public List<Element> elements = new List<Element>();          public List<Element> Elements          {              get { return elements; }          }            public Graphical()          {              Element element = new Rectangle(10,5);              Element element1= new Circular(5);              elements.Add(element);              elements.Add(element1);          }      }  }

 

namespace Visitor_Pattern  {      class Program      {          static void Main(string[] args)          {              Graphical graphical = new Graphical();              foreach (var item in graphical.Elements)              {                  item.CalculatedArea();              }            }      }  }

 

  我们可以看到实现起来还是较为简单的。但是如果这里需求进行了变动,需要加上图形的周长并且输出、同时输出其参数。这就麻烦了。就需要对基类进行更改然后修改子类。就感觉有点得不偿失了。

  我们再看采用模式实现:

namespace Visitor_Pattern  {      class UseVisitorPattern      {      }        #region 访问者        /// <summary>      /// 抽象访问者      /// </summary>      public interface IVistor      {          void Visit(UseRectangle rectangle);          void Visit(UseCircular  useCircular);      }        /// <summary>      /// 具体访问者      /// </summary>      public class Vistor : IVistor      {          public void Visit(UseRectangle rectangle)          {              rectangle.CalculatedArea();              Console.WriteLine($"长方形长是:{rectangle._long}");              Console.WriteLine($"长方形宽是:{rectangle._wide}");              Console.WriteLine($"长方形周长是:{2*(rectangle._long+rectangle._wide)}");          }            public void Visit(UseCircular useCircular)          {              useCircular.CalculatedArea();              Console.WriteLine($"圆形的半径是:{useCircular._r}");              Console.WriteLine($"圆形的周长是是:{2*Math.PI*useCircular._r}");          }      }        #endregion        #region 节点类        /// <summary>      /// 抽象节点类      /// </summary>      public abstract class UseElement      {          public abstract void Accept(IVistor vistor);          public abstract void CalculatedArea();      }        /// <summary>      /// 长方形计算面积输出      /// </summary>      public class UseRectangle : UseElement      {          public double _long;          public double _wide;            public UseRectangle(double Long, double Wide)          {              this._long = Long;              this._wide = Wide;          }            public override void Accept(IVistor vistor)          {              vistor.Visit(this);          }            public override void CalculatedArea()          {              Console.WriteLine($"长方形面积是:{_long * _wide}");          }      }        /// <summary>      /// 圆形计算面积输出      /// </summary>      public class UseCircular : UseElement      {          public double _r;            public UseCircular(double r)          {              this._r = r;          }          public override void Accept(IVistor vistor)          {              vistor.Visit(this);          }          public override void CalculatedArea()          {              Console.WriteLine($"圆形面积是:{Math.PI * _r * _r}");          }      }        #endregion        /// <summary>      /// 结构对象      /// </summary>      public class UseGraphical      {          public List<UseElement> elements = new List<UseElement>();          public List<UseElement> Elements          {              get { return elements; }          }            public UseGraphical()          {              UseElement element = new UseRectangle(10, 5);              UseElement element1 = new UseCircular(5);              elements.Add(element);              elements.Add(element1);          }      }  }

 

namespace Visitor_Pattern  {      class Program      {          static void Main(string[] args)          {                UseGraphical graphical = new UseGraphical();              foreach (var item in graphical.Elements)              {                  item.Accept(new Vistor());              }          }      }  }

  这里我们对每个节点都加入了访问者,这样我们需求变动增加周长和参数的输出的时候修改增加具体访问者就可以实现了。

使用场景及优缺点

一、使用场景

1、对象结构中对象对应的类较少改变、但是会经常在此对象结构上定义新的操作

2、需要对一个对象结构中进行一些不相关的操作、需要在新增操作时避免改变其原来的类

二、优点

1、符合单一职责原则。每个类负责一个职责

2、具有优秀的扩展性和灵活性、添加新的操作会变得较为容易、同时也不会改变其原来的结构代码

3、访问者模式将一些相关的行为操作集合在了访问者对象中,并没有分散在其元素类中

三、缺点

1、具体元素对访问者公开了细节,违背了迪米特原则

2、增加具体元素节点变得困难、与之随之增加的就是在访问者中新增。

总结

  访问者模式就介绍到这里啦。访问者模式主要是将数据结构及操作分离、解决了稳定的数据结构和容易变化的操作的耦合性。在新增操作的时候不修改原来的结构对象的类。直接修改访问者对象中的操作即可新增操作。


      如果我们想要更多的玫瑰花,就必须种植更多的玫瑰树。

     C#设计模式系列目录

        欢迎大家扫描下方二维码,和我一起踏上设计模式的闯关之路吧!