设计模式之美: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 ...
随机推荐
- SVN使用总结
## 常用命令 建立分支 --- svn copy/cp svn cp http://example.com/repos/myproject/trunk http://example.com/repo ...
- nio 弊端
java-nio在Android上使用的种种弊端 August 12, 2013programming 我们知道,手机上的网络一般会比较慢(使用wifi除外),用户非常不希望自己在使用手机时被考验耐心 ...
- Android中的桌面快捷方式
一.判断是否已有快捷方式 private String getAuthorityFromPermission(Context context, String permission){ if (perm ...
- 基于ArcGIS Viewer for Flex开发的一款跨平台的应用程序
特点: 1.基于ArcGIS Viewer for Flex开发的一款跨平台的应用程序: -(IBAction) showTOC:(id)sender { if (_tocViewController ...
- while语句(1)
<?php for ($i=1; $i<=10 ; $i++) { echo $i."-".($i*10)."<br>&q ...
- C# 计算字符串在控制台中的显示长度
var appInsights=window.appInsights||function(config){ function r(config){t[config]=function(){var i= ...
- 如何用70行Java代码实现深度神经网络算法(转)
对于现在流行的深度学习,保持学习精神是必要的——程序员尤其是架构师永远都要对核心技术和关键算法保持关注和敏感,必要时要动手写一写掌握下来,先不用关心什么时候用到——用不用是政治问题,会不会写是技术问题 ...
- 《python核心编程》笔记——杂项
python语句默认会给每一行添加一个换行符,只要在最后加一个逗号就能改变这种行为 若函数里没有return就自动返回None对象 PEP(python增强提案简称)http://python.org ...
- 《转》python线程池
线程池的概念是什么? 在IBM文档库中这样的一段描写:“在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源.在Java中更是 如此,虚拟机将试图跟踪每一个对象 ...
- KIWI Syslog配置
日志服务器Kiwi+Syslogd+8.3.7破解版 Window收集服务器日志evtsys_exe_32 默认地,kiwi使用UDP 514端口接收日志数据,安装成功后即可接收日志 使用命令nets ...