访问者模式倒转了面向对象模型的做法,创建一个外部对象来操作其他类中的数据。即把应该属于一个类的操作放在另一个类的内部。
访问每个类则意味着调用一个已经为此目的而事先安排好的被称作accept的方法。accept方法有一个参数:访问者实例。相应的反过来,被访问类在调用访问者的visit方法,这样就把自身作为参数传递给了访问者。
public virtual void accept(Visitor v) {
v.visit(this);
}
以这种方式,访问者对象一个接一个的接收到每个实例的引用,这样就能调用实例的公有方法获取数据。如果类没有一个accept方法,那么可以子类化它并为它添加一个这样的方法。
当药操作的数据被包含在一些有着不同接口的对象时,你应该考虑使用这个模式。如果为类库或者框架添加功能是,或者是没有源代码,或者其他的技术导致不能修改源代码,此时访问者模式就是一种有助于解决问题的途径。这样我们只需要子类化框架的类并为每个子类添加accept方法就可以了。
我们实例,一个通过访问者访问员工的休假天数的问题。我们的Employee的定义:
using System;
namespace Visitor
{
/// <summary>
/// Summary description for Employee.
/// </summary>
public class Employee {
int sickDays, vacDays;
float salary;
string name;
public Employee(string name, float salary, int vDays, int sDays) {
this.name = name;
this.salary = salary;
sickDays = sDays;
vacDays = vDays;
}
//-----
public string getName() {
return name;
}
public int getSickDays() {
return sickDays;
}
public int getVacDays() {
return vacDays;
}
public float getSalary() {
return salary;
}
public virtual void accept(Visitor v) {
v.visit(this);
}
}
}
这里已经包含了accept方法,在Visitor基类中我们要包含所有的可能派生的访问类需要传递的参数类型的方法原型。
using System;
namespace Visitor
{
/// <summary>
/// Summary description for Visitor.
/// </summary>
public abstract class Visitor {
public abstract void visit(Employee emp);
public abstract void visit(Boss bos);
}
}
然后是一个具体的访问者的定义:
using System;
namespace Visitor
{
/// <summary>
/// Summary description for VacationVisitor.
/// </summary>
public class VacationVisitor : Visitor {
private int totalDays;
//-----
public VacationVisitor() {
totalDays = 0;
}
//-----
public int getTotalDays() {
return totalDays;
}
//-----
public override void visit(Employee emp){
totalDays += emp.getVacDays ();
}
//-----
public override void visit(Boss bos){
totalDays += bos.getVacDays ();
}
}
}
这里在调用visit方法的时候,可以选择行的调用为员工和老板设置的visit函数。
存在多种有着不同接口的 不同的类,我们又希望封装从这些类中获取数据的方式时,访问者就变得更加有用。假设我们的Boss类还可以获得额外的奖励假期。
using System;
namespace Visitor
{
/// <summary>
/// Summary description for Boss.
/// </summary>
public class Boss : Employee {
private int bonusDays;
public Boss(string name, float salary, int vdays, int sdays):base(name, salary, vdays, sdays)
{ }
public void setBonusDays(int bdays) {
bonusDays = bdays;
}
public int getBonusDays() {
return bonusDays;
}
public override void accept(Visitor v ) {
v.visit(this);
}
}
}
我们必须重写这个类的accept函数,否则不重写的话将要给Visitor类返回的是父类的引用,不能达到正确的功能。
然后对这个能获得额外假期的类的访问者的定义如下:
using System; namespace Visitor { /// <summary> /// Summary description for bVacationVisitor. /// </summary> public class bVacationVisitor :Visitor { private int totalDays; public bVacationVisitor() { totalDays = 0; } //----- public override void visit(Employee emp) { totalDays += emp.getVacDays(); try { Manager mgr = (Manager)emp; totalDays += mgr.getBonusDays(); } catch(Exception ){} } //----- public override void visit(Boss bos) { totalDays += bos.getVacDays(); totalDays += bos.getBonusDays(); } //----- public int getTotalDays() { return totalDays; } } }
访问者类将要访问的每个类都实现了visit方法,这样就不用依赖继承调用父类的方法。每个派生类也都要有自己的accept方法,而不是调用父类的方法。
主程序的构造:
private System.ComponentModel.Container components = null; private Employee[] empls; private Employee empl ; private System.Windows.Forms.Button btCompute; private System.Windows.Forms.ListBox lsVac; private Boss bos; public Form1() { InitializeComponent(); init(); } private void init() { int i = 0; empls = new Employee [7]; empls[i++] = new Employee("Susan Bear", 55000, 12, 1); empls[i++] = new Employee("Adam Gehr", 150000, 9, 0); empls[i++] = new Employee("Fred Harris", 50000, 15, 2); empls[i++] = new Employee("David Oakley", 57000, 12, 2); empls[i++] = new Employee("Larry Thomas", 100000, 20, 6); bos = new Boss("Leslie Susi", 175000, 16, 4); bos.setBonusDays(12); empls[i++] = bos; bos = new Boss("Laurence Byerly", 35000, 17, 6); bos.setBonusDays(17); empls[i++] = bos; }
访问者的调用:
private void btCompute_Click(object sender, System.EventArgs e) { VacationVisitor vac = new VacationVisitor(); bVacationVisitor bvac = new bVacationVisitor(); for (int i = 0; i< empls.Length; i++) { empls[i].accept(vac); //get the employee empls[i].accept(bvac); } lsVac.Items.Add("Total vacation days=" + vac.getTotalDays().ToString()); lsVac.Items.Add("Total boss vacation days=" + bvac.getTotalDays().ToString()); }
这样,在嗲用雇员数组的中雇员实例的accept的时候,accept方法会自动调用访问者的visit方法,然后填充访问者中的数据,下一步就可以直接是恶用访问者中的填充好的数据了。
在accept函数中,双向的调用允许你在一个拥有accept方法的类上增加更多的操作,因为我们编写的每个新的访问者类都是可以使用这些类提供的数据完成任何我们可以想到的操作。
访问者可以在不修改类的情况下为类提供附加的功能,访问者能够为类的集合增加功能,并能封装他所使用的方法。