行为型模式——访问者(Visitor)
问题背景
当希望在不改变类的前提下增加新的操作时,考虑使用访问者。假设我们自己实现了一个线性表和一个链表,它们已经有了基本的增删改查操作。现在我们想加入排序功能和映射功能(将表中的每个元素x变为f(x)),但又不想直接改原有的类。
解决方案
为了不修改原有的类,我们只能把新功能加到另一个类中,这个类就称为访问者。访问者通过VisitClassXXX(XXX)方法来操作对应类的对象,来达到扩展的目的。但是问题来了,仅仅这样的话,我们在使用新功能时的写法是这样的:
var xxx = new XXX();
var visitor = new Visitor();
visitor.VisitXXX(xxx);
看起来很别扭是不是?明明在调用xxx的操作,xxx却作为参数,这显然是面向过程的写法。那为了把这种写法变成面向对象式的,我们还是必须在原有类上稍微加点东西的。
在原有的类中加入一个Accept(Visitor)方法,这个方法调用Visitor参数的Visit方法。这样一来,代码就会变成这样:
var xxx = new XXX();
var visitor = new Visitor();
xxx.Accept(visitor);
使用访问者后,程序的结构会变复杂,如下:
效果
- 可以在扩展类的同时避免修改原有代码。
- 可以把一组相关的逻辑聚集在一个类中。
缺陷
访问者有几个明显的缺点:首先,它把可访问的类型硬编码到接口中,从而导致很难增加新的可访问类型;其次,无法进行一些需要访问私有成员的复杂操作,如果强行用反射获取私有成员,则会破坏类的封装性;最后,访问者的存在会大幅增加系统的复杂度。
相关模式
- 复合:访问者可以和复合结构配合来遍历操作其中的每个元素。
- 解释器:访问者经常用来扩展解释器。
实现
using System;
namespace Visitor
{
class Client
{
public interface IList
{
void Accept(IVisitor visitor);
}
public interface IVisitor
{
void VisitArrayList(ArrayList list);
void VisitLinkedList(LinkedList list);
}
public class ArrayList : IList
{
public void Accept(IVisitor visitor)
{
visitor.VisitArrayList(this);
}
}
public class LinkedList : IList
{
public void Accept(IVisitor visitor)
{
visitor.VisitLinkedList(this);
}
}
public class SortVisitor : IVisitor
{
public void VisitArrayList(ArrayList list)
{
Console.WriteLine("线性表排序");
}
public void VisitLinkedList(LinkedList list)
{
Console.WriteLine("链表排序");
}
}
public class MapVisitor : IVisitor
{
public void VisitArrayList(ArrayList list)
{
Console.WriteLine("线性表映射");
}
public void VisitLinkedList(LinkedList list)
{
Console.WriteLine("链表映射");
}
}
static void Main(string[] args)
{
var alist = new ArrayList();
var llist = new LinkedList();
var svisitor = new SortVisitor();
var mvisitor = new MapVisitor();
alist.Accept(svisitor);
alist.Accept(mvisitor);
llist.Accept(svisitor);
llist.Accept(mvisitor);
}
}
}