察看下面的示例,假设我们将交通工具作为父类,汽车、船和飞机作为子类,结构如图
代码如下:
using System.Collections.Generic;
using System.Text;
namespace VisitorPattern.ex34_0
{
public class Vehicle
{
public virtual void Go()
{
Console.WriteLine("交通工具走!");
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace VisitorPattern.ex34_0
{
public class Boat:Vehicle
{
public override void Go()
{
Console.WriteLine("船在水上开!");
}
public void Pull()
{
Console.WriteLine("找个码头停船吧!");
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace VisitorPattern.ex34_0
{
public class Car:Vehicle
{
public override void Go()
{
Console.WriteLine("汽车在路上走!");
}
public void Park()
{
Console.WriteLine("找个地方停车吧!");
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace VisitorPattern.ex34_0
{
public class Plane : Vehicle
{
public override void Go()
{
Console.WriteLine("飞机在天上飞!");
}
public void Land()
{
Console.WriteLine("找个机场降落吧!");
}
}
}
交通工具都是可以“走”的,但不同类新的交通工具有各自的特点,如轮船要靠岸,飞机要降落等。因此在子类中各自有各自的方法,这时子雷扩充了父类。即子类具有比父类更丰富的功能,如何使用这些功能?
一方面我们面向同一的接口,同时希望使用子类扩展的功能。为此不得不判断类型,并且需要强制转换,下面是示例代码:
{
ex34_0.Vehicle myCar = new ex34_0.Car();
ex34_0.Vehicle myBoat = new ex34_0.Boat();
ex34_0.Vehicle myPlane = new ex34_0.Plane();
GoAndStop(myCar);
GoAndStop(myBoat);
GoAndStop(myPlane);
}
static void GoAndStop(ex34_0.Vehicle v)
{
v.Go();
if (v.GetType() == typeof(ex34_0.Car)) ((ex34_0.Car)v).Park();
if (v.GetType() == typeof(ex34_0.Boat)) ((ex34_0.Boat)v).Pull();
if (v.GetType() == typeof(ex34_0.Plane)) ((ex34_0.Plane)v).Land();
}
由于GoAndStop的传入类型是Vehicle,因此需要判断具体的类型并且实行强制转换才能执行子类的功能。在GoAndStop中显然已经破坏了面向接口,因为必须针对美中实现执行特定的编码,有没有更好的解决方法呢?
意图
实现通过统一的接口访问不同类型元素的操作,并且通过这个接口可以增加新的操作而不改变元素的类。
使用场合
当很多对象的接口不同,而我们希望通过这些对象有依赖于具体对象的操作时,可以使用访问者模式。
结构
效果
首先需要说明非常重要的采用访问者模式的第一个代价是使用模式适用于雷机构稳定的系统。结构稳定指类和子类一旦确定,就不需要经常修改,但其中也包括增加新的子类。从上面的结构可以看出,访问者依赖于结构中的所有子类。一旦子类变化,访问及相应的子类就要发生变化,这显然是不划算俄,如果这样,还不如直接修改类结构。
第2个代价是有可能破坏封装性。访问者为对象增加了新的操作,与在对象内直接增加操作有很大不同。这些操作只能通过共有方法或属性访问对象,很多场合下这显然是不够的。
为了使访问者能够访问内部变量,对象有可能不得公开这些变量,从而可能破坏封装性。
访问者带来的好处如下。
可以很容易地为对象结构增加新的操作,通过定义一个新的访问者即可在对象机构不变的情况下增加新操作。
使对象结构的接口统一,通过将无关的操作分离,使类结构的接口更趋近统一。
通过相同的的访问者接口可以获得不同的操作,而且这些操作可以是动态增加的。
可以访问结构中任何层次的对象,甚至不具有相同父类的对象也可以用同一个访问者访问,这种情况可以通过定义一个访问接口实现。
实现
采用访问者模式重构交通工具的示例代码,使用访问者调用具体类的特定方法使这些方法不向客户暴露,如图。
Visitor的接口如下:
using System.Collections.Generic;
using System.Text;
namespace VisitorPattern.ex34_2
{
public abstract class VehicleVisitor
{
public abstract void VisitCar(Car c);
public abstract void VisitBoat(Boat b);
public abstract void VisitPlane(Plane p);
}
}
具体Visitor的代码如下:
using System.Collections.Generic;
using System.Text;
namespace VisitorPattern.ex34_2
{
public class VehicleStopVisitor:VehicleVisitor
{
public override void VisitCar(Car c)
{
c.Park();
}
public override void VisitBoat(Boat b)
{
b.Pull();
}
public override void VisitPlane(Plane p)
{
p.Land();
}
}
}
在原有的Vehicle中增加了接受访问者的方法:
using System.Collections.Generic;
using System.Text;
namespace VisitorPattern.ex34_2
{
public class Vehicle
{
public virtual void Go()
{
Console.WriteLine("交通工具走!");
}
public virtual void Accept(VehicleVisitor v) { }
}
}
具体类决定调用访问者中的方法:
using System.Collections.Generic;
using System.Text;
namespace VisitorPattern.ex34_2
{
public class Boat:Vehicle
{
public override void Go()
{
Console.WriteLine("船在水上开!");
}
public void Pull()
{
Console.WriteLine("找个码头停船吧!");
}
public override void Accept(VehicleVisitor v)
{
v.VisitBoat(this);
}
}
}
using System.Collections.Generic;
using System.Text;
namespace VisitorPattern.ex34_0
{
public class Car:Vehicle
{
public override void Go()
{
Console.WriteLine("汽车在路上走!");
}
public void Park()
{
Console.WriteLine("找个地方停车吧!");
}
}
}
using System.Collections.Generic;
using System.Text;
namespace VisitorPattern.ex34_0
{
public class Plane : Vehicle
{
public override void Go()
{
Console.WriteLine("飞机在天上飞!");
}
public void Land()
{
Console.WriteLine("找个机场降落吧!");
}
}
}
使用代码
{
VisitorPattern.ex34_2.Vehicle myCar = new VisitorPattern.ex34_2.Car();
VisitorPattern.ex34_2.Vehicle myBoat = new VisitorPattern.ex34_2.Boat();
VisitorPattern.ex34_2.Vehicle myPlane = new VisitorPattern.ex34_2.Plane();
VisitorPattern.ex34_2.VehicleStopVisitor v = new VisitorPattern.ex34_2.VehicleStopVisitor();
myCar.Accept(v);
myBoat.Accept(v);
myPlane.Accept(v);
}
如果子类的方法需要扩展,可以引入新的Visitor类型。而客户端不用修改,这就是使用Visitor的好处。
相关模式:
组合模式:访问者可以访问采用组合模式定义的对象结构。
解释器模式:访问者经常用于解释器的实现。