设计模式之美: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 ...
随机推荐
- MJPhotoBrowser BUG修复
崩溃在loading.progress = (float)receivedSize/expectedSize; 分析:MJPhotoView 执行了hide移除了MJPhotoLoadingView, ...
- QT5.2.1大BUG
本来以为5.2.1是release版本 谁知道编译某个程序,执行老是crash 换5.3.2就ok了. 坑啊
- Winform主窗体设计
主窗体顶部为菜单按钮,子窗体内嵌入Panel显示 界面如下: 第二步,主窗体离不开的几个方法 1,点击菜单功能,加载子窗体 private void btnOpenForm_Click(object ...
- fuse入门
参考1 http://www.cs.nmsu.edu/~pfeiffer/fuse-tutorial/html/running.html 参考2 http://www.maastaar.net/fus ...
- C# Susan边缘检测(Susan Edge Detection)
Susan边缘检测,方法简单,效率高,具体参照 The SUSAN Edge Detector in Detail, 修改dThreshold值,可以改变检测效果,用参照提供的重心法.力矩法可得到边缘 ...
- max min 与 min max 的差别
在求解最优化问题时,遇到一个对偶问题的转换:对于形如 的问题,可以转换为求解 即原问题的对偶问题.而在一般情况下: 对于这个为题的说明我参照http://math.stackexchange.com/ ...
- python模块及包的导入
一.模块 通常模块为一个文件,直接使用import来导入就好了.可以作为module的文件类型有".py".".pyo".".pyc".&q ...
- 【记忆化搜索】bzoj1652 [Usaco2006 Feb]Treats for the Cows
跟某NOIP的<矩阵取数游戏>很像. f(i,j)表示从左边取i个,从右边取j个的答案. f[x][y]=max(dp(x-1,y)+a[x]*(x+y),dp(x,y-1)+a[n-y+ ...
- 通过 JDBC 驱动程序使用大容量复制
Microsoft SQL Server 包含一个名为 bcp 的受欢迎的命令行实用工具,以便将较大文件快速大容量复制到 SQL Server 数据库的表或视图中. SQLServerBulkCopy ...
- 第五章GPIO接口
5.1 GPIO硬件介绍 可以不通过他们输出高低电平或者通过它们读入应交的状态 S3C2410有117个I/O端口,分为A~H共8组:GPA.GPB....GPH S3C2440有130个I/O端口, ...