设计模式之美:Visitor(访问者)
索引
意图
表示一个作用于某对象结构中的各元素的操作。
Visitor 使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
Represent an operation to be performed on the elements of an object structure.
Visitor lets you define a new operation without changing the classes of the elements on which it operates.
结构

参与者
Visitor
- 为该对象结构中 ConcreteElement 的每一个类声明一个 Visit 操作。该操作的名字和特征标识了发送 Visit 请求给该访问者的那个类。
ConcreteVisitor
- 实现每个由 Visitor 声明的操作。
Element
- 定义一个 Accept 操作,它以一个 Visitor 为参数。
ConcreteElement
- 实现 Accept 操作,该操作以一个 Visitor 为参数。
ObjectStructure
- 能枚举 Element。
- 可以提供一个高层的接口以允许该 Visitor 访问它的元素。
- 可以是一个 Composite 或是一个集合、列表或无序集合。
适用性
在以下情况下可以使用 Visitor 模式:
- 一个对象结构包含很多类操作,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
- 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作污染这些对象的类。
- 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。
缺点
- 增加新的 ConcreteElement 类很困难。添加新的 ConcreteElement 都要在 Visitor 中添加一个新的抽象操作。
- 可能破坏封装。Visitor 假定 ConcreteElement 接口的功能足够强,足以让 Visitor 进行它的工作。但有时会迫使你提供 ConcreteElement 的内部状态的公共操作。
效果
- Visitor 模式使得易于增加新的操作。
- Visitor 集中相关的操作而分离无关的操作。
相关模式
- Visitor 可以用于对一个 Composite 模式定义的对象结构进行操作。
- Visitor 可以用于 Interpreter 解释。
实现
实现方式(一):Visitor 模式结构样式代码。
namespace VisitorPattern.Implementation1
{
public abstract class Element
{
public abstract void Accept(Visitor visitor);
} public abstract class Visitor
{
public abstract void Visit(ConcreteElementA element);
public abstract void Visit(ConcreteElementB element);
} public class ObjectStructure
{
private List<Element> _elements = new List<Element>(); public void Attach(Element element)
{
_elements.Add(element);
} public void Detach(Element element)
{
_elements.Remove(element);
} public void Accept(Visitor visitor)
{
foreach (var element in _elements)
{
element.Accept(visitor);
}
}
} public class ConcreteElementA : Element
{
public string Name { get; set; } public override void Accept(Visitor visitor)
{
visitor.Visit(this);
}
} public class ConcreteElementB : Element
{
public string ID { get; set; } public override void Accept(Visitor visitor)
{
visitor.Visit(this);
}
} public class ConcreteVisitorA : Visitor
{
public override void Visit(ConcreteElementA element)
{
Console.WriteLine(
"ConcreteVisitorA visited ConcreteElementA : {0}",
element.Name);
} public override void Visit(ConcreteElementB element)
{
Console.WriteLine(
"ConcreteVisitorA visited ConcreteElementB : {0}",
element.ID);
}
} public class ConcreteVisitorB : Visitor
{
public override void Visit(ConcreteElementA element)
{
Console.WriteLine(
"ConcreteVisitorB visited ConcreteElementA : {0}",
element.Name);
} public override void Visit(ConcreteElementB element)
{
Console.WriteLine(
"ConcreteVisitorB visited ConcreteElementB : {0}",
element.ID);
}
} public class Client
{
public void TestCase1()
{
var objectStructure = new ObjectStructure(); objectStructure.Attach(new ConcreteElementA());
objectStructure.Attach(new ConcreteElementB()); objectStructure.Accept(new ConcreteVisitorA());
objectStructure.Accept(new ConcreteVisitorB());
}
}
}
实现方式(二):使用 Visitor 模式解构设计。
假设我们有一个 Employee 类,Employee 分为按时薪计算的 Employee 和按月薪计算的 Employee。
public class Employee
{
public abstract string GetHoursAndPayReport();
} public class HourlyEmployee : Employee
{
public override string GetHoursAndPayReport()
{
// generate the line for this hourly employee
return "100 Hours and $1000 in total.";
}
} public class SalariedEmployee : Employee
{
public override string GetHoursAndPayReport()
{
// do nothing
return string.Empty;
}
}
这段代码的问题是,Employee 类及子类耦合了 Salary Report 相关的职责,这侵犯了单一职责原则(Single Responsibility Principle),因为其导致每次需要更改 Report 相关的职责时,都需要修改 Employee 类。
我们是用 Visitor 模式来解决这个问题。
namespace VisitorPattern.Implementation2
{
public abstract class Employee
{
public abstract string Accept(EmployeeVisitor visitor);
} public class HourlyEmployee : Employee
{
public override string Accept(EmployeeVisitor visitor)
{
return visitor.Visit(this);
}
} public class SalariedEmployee : Employee
{
public override string Accept(EmployeeVisitor visitor)
{
return visitor.Visit(this);
}
} public abstract class EmployeeVisitor
{
public abstract string Visit(HourlyEmployee employee);
public abstract string Visit(SalariedEmployee employee);
} public class HoursPayReport : EmployeeVisitor
{
public override string Visit(HourlyEmployee employee)
{
// generate the line of the report.
return "100 Hours and $1000 in total.";
} public override string Visit(SalariedEmployee employee)
{
// do nothing
return string.Empty;
}
}
}
实现方式(三):使用 Acyclic Visitor 模式解构设计。
我们注意到 Employee 类依赖于 EmployeeVisitor 基类。而 EmployeeVisitor 类为每个 Employee 的子类都提供了一个 Visit 方法。
因此,这里形成了一个依赖关系的环。这导致 Visitor 在响应变化时变得复杂。
Visitor 模式在类继承关系不是经常变化时可以工作的很好,但在子类衍生频繁的情况下会增加复杂度。
此时,我们可以应用 Acyclic Visitor 模式,抽象出窄接口,以使 Employee 子类仅依赖于该窄接口。
namespace VisitorPattern.Implementation3
{
public abstract class Employee
{
public abstract string Accept(EmployeeVisitor visitor);
} public class HourlyEmployee : Employee
{
public override string Accept(EmployeeVisitor visitor)
{
try
{
IHourlyEmployeeVisitor hourlyEmployeeVisitor = (IHourlyEmployeeVisitor)visitor;
return hourlyEmployeeVisitor.Visit(this);
}
catch (InvalidCastException ex)
{
Console.WriteLine(ex.Message);
} return string.Empty;
}
} public class SalariedEmployee : Employee
{
public override string Accept(EmployeeVisitor visitor)
{
try
{
ISalariedEmployeeVisitor salariedEmployeeVisitor = (ISalariedEmployeeVisitor)visitor;
return salariedEmployeeVisitor.Visit(this);
}
catch (InvalidCastException ex)
{
Console.WriteLine(ex.Message);
} return string.Empty;
}
} public interface IHourlyEmployeeVisitor
{
string Visit(HourlyEmployee employee);
} public interface ISalariedEmployeeVisitor
{
string Visit(SalariedEmployee employee);
} public abstract class EmployeeVisitor
{
} public class HoursPayReport : EmployeeVisitor, IHourlyEmployeeVisitor
{
public string Visit(HourlyEmployee employee)
{
// generate the line of the report.
return "100 Hours and $1000 in total.";
}
} public class SalariedPayReport : EmployeeVisitor, ISalariedEmployeeVisitor
{
public string Visit(SalariedEmployee employee)
{
return "Something";
}
}
}
参考文章
《设计模式之美》为 Dennis Gao 发布于博客园的系列文章,任何未经作者本人同意的人为或爬虫转载均为耍流氓。
设计模式之美:Visitor(访问者)的更多相关文章
- 设计模式23:Visitor 访问者模式(行为型模式)
Visitor 访问者模式(行为型模式) 动机(Motivation)在软件构造过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为(方法),如果直接在基类中做这样的修改,将会给子类带来繁重的 ...
- 设计模式学习笔记——Visitor 访问者模式
1.定义IVisitor接口,确定变化所涉及的方法 2.封装变化类.实现IVisitor接口 3.在实体类的变化方法中传入IVisitor接口,由接口确定使用哪一种变化来实现(封装变化) 4.在使用时 ...
- 浅谈设计模式-visitor访问者模式
先看一个和visitor无关的案例.假设你现在有一个书架,这个书架有两种操作,1添加书籍2阅读每一本书籍的简介. //书架public class Bookcase { List<Book> ...
- 设计模式之美:Null Object(空对象)
索引 意图 结构 参与者 适用性 效果 相关模式 实现 实现方式(一):Null Object 的示例实现. 意图 通过对缺失对象的封装,以提供默认无任何行为的对象替代品. Encapsulate t ...
- 设计模式之美:Extension Object(扩展对象)
索引 意图 结构 参与者 适用性 效果 相关模式 实现 实现方式(一):使用示例结构实现 Extension Object. 实现方式(二):使用泛型实现 IExtensibleObject<T ...
- 设计模式之美:Interpreter(解释器)
索引 意图 结构 参与者 适用性 效果 相关模式 实现 实现方式(一):Interpreter 模式结构样式代码. 实现方式(二):解释波兰表达式(Polish Notation). 意图 给定一个语 ...
- 设计模式之美:Composite(组合)
索引 意图 结构 参与者 适用性 缺点 效果 相关模式 实现 实现方式(一):在 Component 中定义公共接口以保持透明性但损失安全性. 意图 将对象组合成树形结构以表示 “部分-整体” 的层次 ...
- 《设计模式之美》 <03>面向对象、设计原则、设计模式、编程规范、重构,这五者有何关系?
面向对象 现在,主流的编程范式或者是编程风格有三种,它们分别是面向过程.面向对象和函数式编程.面向对象这种编程风格又是这其中最主流的.现在比较流行的编程语言大部分都是面向对象编程语言.大部分项目也都是 ...
- 设计模式之美:Product Trader(操盘手)
索引 意图 结构 参与者 适用性 效果 相关模式 实现 实现方式(一):Product Trader 的示例实现. 意图 使客户程序可以通过命名抽象超类和给定规约来创建对象. Product Trad ...
随机推荐
- Ext.Net TreePanel 修改Icon图标
分类: Ext.Net2012-09-24 13:44 1779人阅读 评论(0) 收藏 举报 webformserverextassemblyxhtmlobject 1.默认icon 2.自定义ic ...
- python的正负无穷float("inf")的用法
今天,在看书的时候看到这么一个例子: 这是用来求解 从某个数字列表中找出俩个彼此最接近但是不相等的数(俩者之间的绝对差是最小的): >>> from random import ra ...
- 一起买beta版PHP单元测试
一起买beta版PHP单元测试 测试目的 保证代码质量,对各个单元进行测试,可以有效地保证代码的可靠性,让模块在与别的模块整合时出现更少的错误. 单元描述 完成帖子接口 将"正在进行&q ...
- 大前端学习笔记整理【五】rem与px换算的计算方式
前言 这段时间的小项目中算是真正意义上使用了rem来进行移动端的页面布局,项目结束了我反思了一下之前的对于rem的使用...原来我以前对rem用法完全是在搞笑啊!!结合这次这个小项目,我觉得我也有必要 ...
- 收缩数据库 DBCC SHRINKFILE
/* from: http://www.cnblogs.com/blackcore/archive/2010/12/27/1917911.html */ 数据库中的每个文件都可以通过删除未使用的页的方 ...
- Zip压缩和解压缩
这个功能完全依靠一个第三方的类,ICSharpCode.SharpZipLib.dll,只是在网上搜了大半天,都没有关于这个类的详细解释,搜索的demo也是各种错误,感觉作者完全没有跑过,就那么贸贸然 ...
- luogu p2330[SCOI05] 繁忙的都市——瓶颈生成树
P2330 05四川 繁忙的都市 题目描述 城市C是一个非常繁忙的大都市,城市中的道路十分的拥挤,于是市长决定对其中的道路进行改造.城市C的道路是这样分布的:城市中有n个交叉路口,有些交叉路口之间有道 ...
- 深入理解c#(第三版)(文摘)
第一部分 基础知识 第1章 C#开发的进化史 1.3 1.3.1 表示未知的价格 public decimal? Price { get; private set; } new ProductWith ...
- springboot使用之四:错误页面404处理建议
每个项目可能都会遇到404,403,500等错误代码,如没有错误页面,则会给用户一个很不友好的界面,springboot项目同样也存在这个问题. 但在官方文档并没有相关配置信息,这就要求我们自己来实现 ...
- 在64位windows下使用instsrv.exe和srvany.exe创建windows服务[转]
本文转自:https://www.iflym.com/index.php/computer-use/201205020001.html 在32位的windows下,包括windows7,windows ...