索引

意图

表示一个作用于某对象结构中的各元素的操作。

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(访问者)的更多相关文章

  1. 设计模式23:Visitor 访问者模式(行为型模式)

    Visitor 访问者模式(行为型模式) 动机(Motivation)在软件构造过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为(方法),如果直接在基类中做这样的修改,将会给子类带来繁重的 ...

  2. 设计模式学习笔记——Visitor 访问者模式

    1.定义IVisitor接口,确定变化所涉及的方法 2.封装变化类.实现IVisitor接口 3.在实体类的变化方法中传入IVisitor接口,由接口确定使用哪一种变化来实现(封装变化) 4.在使用时 ...

  3. 浅谈设计模式-visitor访问者模式

    先看一个和visitor无关的案例.假设你现在有一个书架,这个书架有两种操作,1添加书籍2阅读每一本书籍的简介. //书架public class Bookcase { List<Book> ...

  4. 设计模式之美:Null Object(空对象)

    索引 意图 结构 参与者 适用性 效果 相关模式 实现 实现方式(一):Null Object 的示例实现. 意图 通过对缺失对象的封装,以提供默认无任何行为的对象替代品. Encapsulate t ...

  5. 设计模式之美:Extension Object(扩展对象)

    索引 意图 结构 参与者 适用性 效果 相关模式 实现 实现方式(一):使用示例结构实现 Extension Object. 实现方式(二):使用泛型实现 IExtensibleObject<T ...

  6. 设计模式之美:Interpreter(解释器)

    索引 意图 结构 参与者 适用性 效果 相关模式 实现 实现方式(一):Interpreter 模式结构样式代码. 实现方式(二):解释波兰表达式(Polish Notation). 意图 给定一个语 ...

  7. 设计模式之美:Composite(组合)

    索引 意图 结构 参与者 适用性 缺点 效果 相关模式 实现 实现方式(一):在 Component 中定义公共接口以保持透明性但损失安全性. 意图 将对象组合成树形结构以表示 “部分-整体” 的层次 ...

  8. 《设计模式之美》 <03>面向对象、设计原则、设计模式、编程规范、重构,这五者有何关系?

    面向对象 现在,主流的编程范式或者是编程风格有三种,它们分别是面向过程.面向对象和函数式编程.面向对象这种编程风格又是这其中最主流的.现在比较流行的编程语言大部分都是面向对象编程语言.大部分项目也都是 ...

  9. 设计模式之美:Product Trader(操盘手)

    索引 意图 结构 参与者 适用性 效果 相关模式 实现 实现方式(一):Product Trader 的示例实现. 意图 使客户程序可以通过命名抽象超类和给定规约来创建对象. Product Trad ...

随机推荐

  1. VBA使用的Randomize和DoEvents

    Randomize private function getInt() dim n,m as integer Randomize n=1 m=3 getInt=Int((m+1-n)*rnd + n) ...

  2. aa3

    var geoCoordMap = { "海门":[121.15,31.89], "鄂尔多斯":[109.781327,39.608266], "招远 ...

  3. PhoneGap--001 入门 安装

    PhoneGap 百度百科 PhoneGap 中文网 3.0 安装使用 今天也配置好phonegap3.0 android开发环境了,操作系统是win7,就楼主文章做些补充. 我是按phonegap官 ...

  4. 引用64位dll时候出现 未能加载文件或程序集“System.Data.SQLite”或它的某一个依赖项。试图加载格式不正确的程序。

    引用64位dll时候出现 未能加载文件或程序集“System.Data.SQLite”或它的某一个依赖项.试图加载格式不正确的程序. 需要在web.config增加配置 <startup use ...

  5. AspNetPager 免费分页控件7.5.1版发布!

    AspNetPager 免费分页控件7.5.1版发布,本次升级主要内容有: 修正了ShowDisabledButtons为false时html闭合标签丢失的bug:改为从System.Web.UI.W ...

  6. OD使用教程5

    怎样恢复数据被改过的代码 点击m进入内存分布图 双击进入文件头 将过大的值一一修改为正常值 普通程序没有动态链接表所以值改为0 保存:首先记住地址 其次 选中差不多的长度,保存 正常的程序

  7. 删除从第i个位置开始,长度为len的子串

    /*字符串采用带头结点的链表存储,设计算法函数void delstring(linkstring s, int i,int len)在字符串s中删除从第i个位置开始,长度为len的子串.*//**** ...

  8. C语言小练习三

    题目要求: 定义一个二维数组保存 10个学生的5门课成绩,分别用函数实现:(1)input():输入每个学生的成绩:(2)output():输出每个学生的成绩:(3)aver_stu():计算并输出每 ...

  9. VS2015调试UWP程序时提示错误DEP0700 : Registration of the app failed. Another user has already installed

    在同一台windows10电脑上调试过一个工程以后,切换了账号再次调试出现错误 DEP0700 : Registration of the app failed. Another user has a ...

  10. 黑马程序员_Java基础:反射机制(Reflection)总结

    ------- android培训.java培训.期待与您交流! ---------- 反射在java中有非常重大的意义,它是一种动态的相关机制,可以于运行时加载.探知.使用编译期间完全未知的clas ...