访问者(Visitor)模式表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
访问者的适用情况:
1、 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
2、 需要对一个对象结构中的对象进行很多不同的并且不想关的操作,而你想避免让这些操作“污染”这些对象的类。
3、 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。
本文以游戏中的仓库为例。仓库中含有两种对象,一个是道具(Item)另外一个是装备(Equipment),这两种对象在访问者模式中称之为元素。我们需要使用访问者模式,来统计仓库中的元素的数量和总价。
物品基类:
public abstract class Stuff
{
public string name { get; private set;}
public int unitPrice { get; private set;}
public Stuff(string name_, int unitPrice_)
{
name = name_;
unitPrice = unitPrice_;
}
public abstract void Accept (IVisitor visitor);
}
这里公开了一个Accept方法用于接收访问者。
访问者接口定义:
public interface IVisitor
{
void VisitItem (Item item);
void VisitEquipment (Equipment equipment);
}
分别需要实现访问道具和访问装备的方法。道具定义:
public class Item : Stuff
{
public Item(string name_, int unitPrice_) : base (name_, unitPrice_)
{
num = 1;
}
public Item(string name_, int unitPrice_, int num_) : base(name_, unitPrice_)
{
num = num_;
}
public int num { get; set;}
public override void Accept(IVisitor visitor)
{
visitor.VisitItem (this);
}
}
装备定义:
public class Equipment : Stuff
{
public Equipment(string name_, int unitPrice_) : base(name_, unitPrice_)
{
}
public override void Accept(IVisitor visitor)
{
visitor.VisitEquipment (this);
}
}
统计数量的访问者:
public class CounterVisitor : IVisitor
{
public int num { get; private set;}
public void VisitItem (Item item)
{
num += item.num;
}
public void VisitEquipment(Equipment equipment)
{
num += 1;
}
}
统计总价的访问者:
public class PriceVisitor : IVisitor
{
public int price {get; private set;}
public void VisitItem(Item item)
{
price += item.num * item.unitPrice;
}
public void VisitEquipment(Equipment equipment)
{
price += equipment.unitPrice;
}
}
接着就是仓库:
public class Inventory
{
List<Stuff> _stuffs = new List<Stuff>();
public void AddStuff(Stuff stuff)
{
_stuffs.Add (stuff);
}
public void RemoveStuff(Stuff stuff)
{
_stuffs.Remove (stuff);
}
public void Accept(IVisitor visitor)
{
foreach (Stuff stuff in _stuffs) {
stuff.Accept (visitor);
}
}
}
仓库也定义了Accept方法,接受visitor,并遍历物品列表,逐个传递visitor。
使用:
Inventory inventory = new Inventory ();
inventory.AddStuff (new Item ("stone", 2, 10));
inventory.AddStuff (new Item ("log", 1, 15));
inventory.AddStuff (new Equipment ("lance", 200));
PriceVisitor priceVisitor = new PriceVisitor ();
inventory.Accept (priceVisitor);
Console.WriteLine (priceVisitor.price);
CounterVisitor counterVisitor = new CounterVisitor ();
inventory.Accept (counterVisitor);
Console.WriteLine (counterVisitor.num);
访问者模式的优点:
1、 易于增加新的操作。增加一个新的访问者即可在一个对象结构上定义一个新的操作。
2、 访问者集中相关的操作而分离无关的操作。所有与操作相关的数据结构和逻辑都被隐藏在访问者之中,而无需在元素中实现,这样既简化了访问者又简化了元素。
3、 可以访问不具有相同父类的对象。
4、 可以累积状态。例如本文示例当中,计算数量和总价就是累积状态的过程。
缺点:
1、 增加一个元素类将会十分困难,因为需要为每一个访问者增加对应的方法。
2、 破坏封装性,这个模式常常迫使你提供访问元素内部状态的公共操作,这可能会破坏它的封装性。
所以,如果不是必要,尽量不要使用这种模式。