前言
访问者模式,是一种将数据的结构与其操作分离的类行为型模式。它能够帮助我们解决数据结构稳定但数据操作多变的问题,使我们可以很容易的增加或修改数据的操作。
在《设计模式 - 可复用的面向对象软件》一书中将之描述为“ 表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作 ”。
结构
- 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)