设计模式学习总结-访问者模式(Visitor Method)
问题:
在面向对象系统的设计和开发过程中,由于需求的改变某些类常常需要增加新的功能,但这些类数据元素的层次结构是相对稳定的,如何在不改变各元素的类的前提下定义作用于这些元素的新操作?最常见就是解决方案就是采用继承的方法给已经设计、实现好的类添加新的方法。这样不停地打补丁,系统设计根本就不可能封闭、编译永远都是整个系统代码。访问者模式把数据结构和作用于结构上的操作解耦合,将容易变更的操作封装到一个类中(访问操作),并由待更改类提供一个接收接口,使得操作集合可相对自由地演化。
定义:
访问者模式(Visitor Pattern)属于行为模式,表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。
意图:
定义一个抽象的访问者(Visitor)角色他定义了所有数据元素的抽象访问方法,可以通过继承此抽象访问者的形式为元素对象访问实现多态,每一个具体的访问者都包含了对所有数据元素访问的相关方法。数据结构的每一个元素都定义了一个可以接受具体访问者的接口方法:Accept(Visitor visitor),这个方法可以通过参数的形式接收一个具体访问者的调用,此元素将它自己传入给具体的访问者:visitor.VisitConcreteElement(this),具体的访问者则根据元素的具体类型执行针此对类型元素的相关操作(这样的过程叫做"双重分派")。通过这样的方法把数据结构和作用于结构上的操作解耦,使得可以在不破坏类的前提下,为类提供增加新的新操作,变得简单,只需加入一个新的Concrete Visitor即可。
参与者:
•抽象访问者(Visitor)角色:
为该对象结构中具体元素角色声明一个访问操作接口。该操作接口的名字和参数标识了发送访问请求给具休访问者的具休元素角色,这样访问者就可以通过该元素角色的特定接口直接访问它。
•具体访问者(ConcreteVisitor)角色:
实现Visitor声明的接口。
•抽象元素(Element)角色:
定义一个接受访问操作(accept()),它以一个访问者(Visitor)作为参数。
•具体元素(ConcreteElement)角色:
实现了抽象元素(Element)所定义的接受操作接口。
•结构对象(ObjectStructure)角色:
这是使用访问者模式必备的角色。它具备以下特性:能枚举它的元素;可以提供一个高层接口以允许访问者访问它的元素;如有需要,可以设计成一个复合对象或者一个聚集(如一个列表或无序集合)。
UML:
代码说明:
/// 访问者抽象类Visitor
/// 可以通过继承的方法为对象实现多态。
/// </summary>
public abstract class Visitor
{
/// <summary>
/// 定义访问ConcreteElementA对象的抽象方法
/// </summary>
/// <param name="concreteElementA"></param>
public abstract void VisitConcreteElementA(ConcreteElementA concreteElementA);
/// <summary>
/// 定义访问ConcreteElementA对象的抽象方法
/// </summary>
/// <param name="concreteElementB"></param>
public abstract void VisitConcreteElementB(ConcreteElementB concreteElementB);
}
/// <summary>
/// ConcreteVisitorA,具体的访问类,怎么访问 ConcreteElementA,ConcreteElementB有具体的访问类决定。
/// </summary>
public class ConcreteVisitorA : Visitor
{
/// <summary>
/// 定义访问ConcreteElementA对象的具体方法
/// </summary>
/// <param name="concreteElementA"></param>
public override void VisitConcreteElementA(ConcreteElementA concreteElementA)
{
Console.WriteLine("{0}访问{1}", this.GetType().Name, concreteElementA.GetType().Name);
}
/// <summary>
/// 定义访问ConcreteElementB对象的具体方法
/// </summary>
/// <param name="concreteElementB"></param>
public override void VisitConcreteElementB(ConcreteElementB concreteElementB)
{
Console.WriteLine("{0}访问{1}", this.GetType().Name, concreteElementB.GetType().Name);
}
}
/// <summary>
/// ConcreteVisitorA,具体的访问类,怎么访问 ConcreteElementA,ConcreteElementB有具体的访问类决定。
/// </summary>
public class ConcreteVisitorB : Visitor
{
/// <summary>
/// 定义访问ConcreteElementA对象的具体方法
/// </summary>
/// <param name="concreteElementA"></param>
public override void VisitConcreteElementA(ConcreteElementA concreteElementA)
{
Console.WriteLine("{0}访问{1}", this.GetType().Name, concreteElementA.GetType().Name);
}
/// <summary>
/// 定义访问ConcreteElementB对象的具体方法
/// </summary>
/// <param name="concreteElementB"></param>
public override void VisitConcreteElementB(ConcreteElementB concreteElementB)
{
Console.WriteLine("{0}访问{1}", this.GetType().Name, concreteElementB.GetType().Name);
}
}
/// <summary>
/// 访问元素的抽象类,所有被访问的元素都要实现这个接口
/// </summary>
public abstract class Element
{
/// <summary>
/// 定义一个Accept操作,它以一个访问者为参数
/// </summary>
/// <param name="visitor"></param>
public abstract void Accept(Visitor visitor);
}
/// <summary>
/// 具体元素ConcreteElementB
/// </summary>
public class ConcreteElementA : Element
{
/// <summary>
/// 接收一个访问者,执行具体的访问方法。
/// </summary>
/// <param name="visitor"></param>
public override void Accept(Visitor visitor)
{
visitor.VisitConcreteElementA(this);
}
public void OperationA()
{
}
}
/// <summary>
/// 具体元素ConcreteElementB
/// </summary>
public class ConcreteElementB : Element
{
/// <summary>
/// 接收一个访问者,执行具体的访问方法。
/// </summary>
/// <param name="visitor"></param>
public override void Accept(Visitor visitor)
{
visitor.VisitConcreteElementB(this);
}
public void OperationB()
{
}
}
/// <summary>
/// 对象结构ObjectStructure
/// 关联访问对象与访问方法
/// </summary>
public class ObjectStructure
{
IList<Element> elements = new List<Element>();
public void Add(Element e)
{
elements.Add(e);
}
public void Remove(Element e)
{
elements.Remove(e);
}
public void Accept(Visitor visitor)
{
foreach (Element e in elements)
{
e.Accept(visitor);
}
}
}
//客户端代码
public void VisitorTest()
{
//初始化一个对象结构
ObjectStructure os = new ObjectStructure();
//添加需要访问的对象
os.Add(new ConcreteElementA());
os.Add(new ConcreteElementB());
//定义访问者
ConcreteVisitorA cv1 = new ConcreteVisitorA();
ConcreteVisitorB cv2 = new ConcreteVisitorB();
//执行访问
os.Accept(cv1);
os.Accept(cv2);
Console.Read();
}
优点:
•增加新的操作就意味着增加一个新的访问者类,因此,问者模式使得增加新的操作变得很容易。
•访问者模式将有关的行为集中到一个访问者对象中,而不是分散到一个个的元素类中。
•每一个单独的访问者对象都集中了所有元素的访问相关的行为,这样有益于系统的管理和维护。
缺点:
•增加新的元素类变得很困难。每增加一个新的元素都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作。
•破坏封装性,元素必须暴露一些可被访问者访问的一些操作和状态,破坏元素对象的封装性。
应用场景:
•如果一个系统有比较稳定的数据结构,又有易于变化的算法的话,使用访问者模式就是比较合适的,因为访问者模式使得算法操作的增加变得容易。