二十三种设计模式[23] - 访问者模式(Visitor Pattern)

前言

访问者模式,是一种将数据的结构与其操作分离的类行为型模式。它能够帮助我们解决数据结构稳定但数据操作多变的问题,使我们可以很容易的增加或修改数据的操作。

在《设计模式 - 可复用的面向对象软件》一书中将之描述为“ 表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作 ”。

结构

  • Visitor(访问者接口):定义了每种元素的访问行为,一般情况下访问行为的个数与元素种类的个数一致;
  • ConcretVisitor(具体访问者):实现访问者接口,实现每种元素具体的访问行为;
  • Element(元素接口或抽象):用来定义以访问者为入参的操作;
  • ConcreteElement(具体元素):用来实现以访问者为入参的操作,该操作通常只是调用访问者的访问行为;
  • ObjectStructure(对象结构):元素的集合或组合,为其内部元素统一接收访问者;

注:组合相关内容可参照组合模式(Composite Pattern)

示例

以操作系统中的目录结构为例,看看如何使用访问者模式来遍历这个树状结构并获得我们想要的信息。类结构如下:

在此结构的基础上,模拟如下目录结构:

实现如下:

  • 结构相关
public interface IFile
{
    void Add(IFile obj);
    void Accept(IFileVisitor visitor);
}

public class Folder : IFile
{
    public string Name { set; get; } = string.Empty;
    private List<IFile> _childList = new List<IFile>();

    public Folder(string name)
    {
        this.Name = name;
    }

    public void Add(IFile obj)
    {
        this._childList.Add(obj);
    }

    public void Accept(IFileVisitor visitor)
    {
        foreach (var item in this._childList)
        {
            item.Accept(visitor);
        }

        visitor.Visit(this);
    }
}

public class Png : IFile
{
    public string Name { set; get; } = string.Empty;

    public Png(string name)
    {
        this.Name = name;
    }

    public void Add(IFile obj)
    {
        throw new NotImplementedException("Sorry,I have not child");
    }

    public void Accept(IFileVisitor visitor)
    {
        visitor.Visit(this);
    }
}

public class Txt : IFile
{
    public string Name { set; get; } = string.Empty;

    public Txt(string name)
    {
        this.Name = name;
    }

    public void Add(IFile obj)
    {
        throw new NotImplementedException("Sorry,I have not Child");
    }

    public void Accept(IFileVisitor visitor)
    {
        visitor.Visit(this);
    }
}
  • 访问者相关
public interface IFileVisitor
{
    void Visit(Folder folder);
    void Visit(Png png);
    void Visit(Txt txt);
}

/// <summary>
/// 统计文件夹个数的访问者
/// </summary>
public class FolderSumVisitor : IFileVisitor
{
    public int Sum { private set; get; } = 0;

    public void Visit(Folder folder) { this.Sum++; }

    public void Visit(Png png) { }

    public void Visit(Txt txt) { }
}

/// <summary>
/// 统计png文件个数的访问者
/// </summary>
public class PngSumVisitor : IFileVisitor
{
    public int Sum { private set; get; } = 0;

    public void Visit(Folder folder) { }

    public void Visit(Png png) { this.Sum++; }

    public void Visit(Txt txt) { }
}

/// <summary>
/// 统计txt文件名称列表的访问者
/// </summary>
public class TxtNameVisitor : IFileVisitor
{
    public List<string> NameList { private set; get; } = new List<string>();

    public void Visit(Folder folder) { }

    public void Visit(Png png) { }

    public void Visit(Txt txt) { this.NameList.Add(txt.Name); }
}
  • 调用
static void Main(string[] args)
{
    //创建目录结构 Start
    IFile folderA = new Folder("FolderA");
    folderA.Add(new Txt("TxtA"));
    folderA.Add(new Png("PngA"));

    IFile folderB = new Folder("FolderB");
    folderB.Add(new Txt("TxtB"));
    folderB.Add(new Png("PngB"));

    IFile folderC = new Folder("FolderC");
    folderC.Add(new Txt("TxtC"));
    folderC.Add(new Png("PngC"));

    folderB.Add(folderC);
    IFile folder = new Folder("Folder");
    folder.Add(folderA);
    folder.Add(folderB);
    //创建目录结构 End

    FolderSumVisitor folderSumVisitor = new FolderSumVisitor();
    folder.Accept(folderSumVisitor);
    Console.WriteLine($"共有文件夹{folderSumVisitor.Sum}个");

    PngSumVisitor pngSumVisitor = new PngSumVisitor();
    folder.Accept(pngSumVisitor);
    Console.WriteLine($"共有png文件{pngSumVisitor.Sum}个");

    TxtNameVisitor txtNameVisitor = new TxtNameVisitor();
    folder.Accept(txtNameVisitor);
    Console.WriteLine($"{Environment.NewLine}所有的txt文件名如下:");
    txtNameVisitor.NameList.ForEach(t => Console.WriteLine(t));

    Console.ReadKey();
}

示例中使用了组合模式(Composite Pattern)来描述这个目录结构。在该结构中,根节点Folder类中的Accept函数将接收到的访问者依次传入其子节点的Accept函数后调用该访问者的Visit函数。而子节点Txt和Png类中的Accept函数则只是调用其接收的访问者的Visit函数。访问者利用重载,通过传入的节点类型来判断具体调用的函数。

由于访问者与结构之间相对独立,所以我们在修改访问者的访问行为时不必对结构做出改动。而当我们需要增加对于该结构的遍历逻辑时,只需要增加对应的访问者即可。改动如下:

看得出来,在访问者模式中增加一个访问者是非常轻松的,并不需要修改其它的文件,复合开闭原则。如果需要增加一个新的元素类型呢?如下。

这种情况下,在增加元素类型的同时需要对所有访问者类做出对应改动!这也是为什么说访问者模式能够帮助我们解决的是数据结构稳定但数据操作多变的问题。

总结

访问者模式将数据的结构与其操作分离,使得操作可以独立变化而不会影响到数据的结构,同时它们的职责也更加明确,复合单一职责原则。这种方式也使得我们在增加一个访问者(操作)时变得异常简单,但在增加元素(结构)时却变得异常恐怖。分离的好处在于相对独立,但也正是因为相对独立的关系,元素又不得不向访问者暴露一些内部的状态和结构。

最后,在使用访问者模式之前一定要确认数据的结构是否足够稳定!!!

以上,就是我对访问者模式的理解,希望对你有所帮助。

示例源码:https://gitee.com/wxingChen/DesignPatternsPractice

系列汇总:https://www.cnblogs.com/wxingchen/p/10031592.html

本文著作权归本人所有,如需转载请标明本文链接(https://www.cnblogs.com/wxingchen/p/10355609.html)

原文地址:https://www.cnblogs.com/wxingchen/p/10355609.html

时间: 2024-08-23 20:22:33

二十三种设计模式[23] - 访问者模式(Visitor Pattern)的相关文章

[设计模式] 23 访问者模式 visitor Pattern

在GOF的<设计模式:可复用面向对象软件的基础>一书中对访问者模式是这样说的:表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作.访问者模式把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由地演化.该模式的目的是要把处理从数据结构分离出来.访问者模式让增加新的操作很容易,因为增加新的操作就意味着增加一个新的访问者.访问者模式将有关的行为集中到一个访问者对象中.   初次接触,定义会显得晦涩并且难于理解,没关系,LZ来陪

二十三种设计模式[12] - 代理模式(Proxy Pattern)

前言 代理模式,属于对象结构型模式.在<设计模式 - 可复用的面向对象软件>一书中将之描述为" 为其它对象提供一种代理以控制对这个对象的访问 ". 在代理模式中,通常使用一个类来代表另一个类的功能,并由这个代理对象去控制原对象的引用. 结构 Subjuet(公共接口):代理类和被代理类的公共接口,保证任何使用目标的地方都可以被代理类替换: RealSubject(被代理类):代理类所代表的目标类: Proxy(代理类):包含对目标类的引用,目标类的封装: 场景 在日常生活中

二十三种设计模式[20] - 状态模式(State Pattern)

前言 状态模式,对象行为型模式的一种.在<设计模式 - 可复用的面向对象软件>一书中将之描述为" 允许一个对象在其内部状态改变时改变它的行为,使对象看起来似乎修改了它的类 ". 场景 我们都坐过火车,火车可以简单的分为" 开门 "," 关门 "," 运行 "," 停止 "四个状态.火车在这四个状态下分别可以做不同的事情.比如只有在关门时才行运行.只有在停止时才能开门. 我们在开发类似的业务时,往

二十三种设计模式[21] - 策略模式(Strategy Pattern)

前言 策略模式,对象行为型模式的一种.在<设计模式 - 可复用的面向对象软件>一书中将之描述为" 定义一些列的算法,把它们一个个封装起来,并且使它们可相互替换.使得算法可以独立于使用它的客户而变化 ". 也就是说通过策略模式,我们能够将算法与其调用者分离成相对独立的个体,降低维护成本,使代码更加优雅. 场景 就拿数据的搜索来说,可以简单的分为模糊搜索和精确搜索.在开发这个功能时,可能会写出如下代码. public List<string> Search(stri

二十三种设计模式之原型模式的C#实现

原型模式就是通过拷贝快速创建一个新的对象 本例UML如图 ColorBase [Serializable] public abstract class ColorBase { public int R = 0; public int G = 0; public int B = 0; public virtual ColorBase Clone() { return this; } public virtual void ShowMe() { Console.WriteLine(string.Fo

二十三种设计模式——工厂模式

二十三种设计模式--工厂模式 简单工厂模式 简单工厂模式又称静态工厂方法(StaticFactory Method)模式,不属于23种模式之一. 简单工厂模式是工厂模式最简单使用的模式. 类图: 程序: #include <iostream> #include<string> using namespace std; class NationalFlag//父类 { public: NationalFlag(){} ~NationalFlag(){} virtual void di

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

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

23种设计模式之访问者模式(Visitor)

访问者模式是一种对象的行为性模式,用于表示一个作用于某对象结构中的各元素的操作,它使得用户可以再不改变各元素的类的前提下定义作用于这些元素的新操作.访问者模式使得增加新的操作变得很容易,但在一定程度上破坏了封装性. 优点: 1)更容易添加新操作. 2)集中相关操作并且排除不相关操作. 使用场景: 1)对象结构包含许多具有不同接口的对象类,并且向要对这些依赖于具体类的对象进行操作. 2)定义对象结构的类很少被修改,但想要在此结构之上定义新的操作. Visitor 模式

二十三种设计模式及其python实现

本文源码寄方于github:https://github.com/w392807287/Design_pattern_of_python 参考文献: <大话设计模式>——吴强 <Python设计模式>——pythontip.com <23种设计模式>——http://www.cnblogs.com/beijiguangyong/ 设计模式是什么? 设计模式是经过总结.优化的,对我们经常会碰到的一些编程问题的可重用解决方案.一个设计模式并不像一个类或一个库那样能够直接作用