访问者模式
- 访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变。
- System.Collection命名空间下提供了大量集合操作对象。但大多数情况下处理的都是同类对象的聚集。
- 如果需要针对一个包含不同类型元素的聚集采取某种操作,而操作的细节根据元素的类型不同而有所不同时,就会出现必须对元素类型做类型判断的条件转移语句。
问题提出
- 在面向对象系统的开发和设计过程,经常会遇到一种情况就是需求变更。
- Visitor模式则提供了一种解决方案:将更新(变更)封装到一个类中(访问操作),并由待更改类提供一个接收接口,则可达到效果。
模式结构图
具体访问者的数目与具体节点的数目没有任何关系。
示意性代码
namespace 访问者模式
{
abstract class Element
{
public abstract void Accept(Visitor visitor);
}
class ConcreteElementA:Element
{
public override void Accept(Visitor visitor)
{
visitor.VisitConcreteElementA(this);
}
public void OperationA() { }
}
class ConcreteElementB : Element
{
public override void Accept(Visitor visitor)
{
visitor.VisitConcreteElementB(this);
}
public void OperationB() { }
}
abstract class Visitor
{
public abstract void VisitConcreteElementA(ConcreteElementA concreteElementA);
public abstract void VisitConcreteElementB(ConcreteElementB concreteElementB);
}
class ConcreteVisitor1:Visitor
{
public override void VisitConcreteElementA(ConcreteElementA concreteElementA)
{
Console.WriteLine("{0}被{1}访问",concreteElementA.GetType().Name,this.GetType().Name);
}
public override void VisitConcreteElementB(ConcreteElementB concreteElementB)
{
Console.WriteLine("{0}被{1}访问", concreteElementB.GetType().Name, this.GetType().Name);
}
}
class ConcreteVisitor2 : Visitor
{
public override void VisitConcreteElementA(ConcreteElementA concreteElementA)
{
Console.WriteLine("{0}被{1}访问", concreteElementA.GetType().Name, this.GetType().Name);
}
public override void VisitConcreteElementB(ConcreteElementB concreteElementB)
{
Console.WriteLine("{0}被{1}访问", concreteElementB.GetType().Name, this.GetType().Name);
}
}
class ObjectStructure
{
private IList<Element> elements = new List<Element>();
public void Attach(Element element)
{
elements.Add(element);
}
public void Detach(Element element)
{
elements.Remove(element);
}
public void Accept(Visitor visitor)
{
foreach(Element e in elements)
{
e.Accept(visitor);
}
}
}
class Program
{
static void Main(string[] args)
{
ObjectStructure o = new ObjectStructure();
o.Attach(new ConcreteElementA());
o.Attach(new ConcreteElementB());
ConcreteVisitor1 v1 = new ConcreteVisitor1();
ConcreteVisitor2 v2 = new ConcreteVisitor2();
o.Accept(v1);
o.Accept(v2);
Console.Read();
}
}
}
结构对象会遍历他自己所保存的聚集中的所有节点,在本系统中就是节点ConcreteElementA和节点ConcreteElementB。首先ConcreteElementA会被访问到,这个访问是由以下的操作组成的:
- ConcreteElementA对象的Accept方法被调用,并将Visitora对象本身传入。
- ConcreteElementA对象翻过来自动阿勇Visitor1对象的访问方法visitConcreteElementA,并将ConcreteElementA对象本身传入。
- 从而就完成了双重分派过程,接着,ConcreteElementB会被访问,这个访问的过程和ConcreteElementA被访问的过程是一样的。
结构对象对剧集元素的遍历过程就是对剧集中所有的节点进行委派的过程,也就是双重分派过程。
注意
- 访问者模式的目的的是要把处理从数据结构分离出来。
- 访问者模式仅应答在被访问的类结构非常稳定的情况下使用。
换言之,系统很少出现需要加入新节点的情况,如果出现需要加入新节点的情况,那么就必须在每一个访问对象里加入一个对应于这个新节点的访问操作,而这个是对一个系统的大规模修改,因而是违背了“开——闭”原则。 - 访问者模式提供了倾斜的可扩展性设计:方法集合的可扩展性和类集合的不嗑扩展性。换言之,如果系统的数据结构是频繁变化的,则不适合使用访问者模式。
优点
- 访问者模式使得增加新的操作变得很容易。增加新的操作就意味着增加一个新的访问者类。
- 访问者模式将有关的行为集中到一个访问者对象中,而不是分散到一个个的节点类中。
缺点
- 增加新的节点变得很困难。每增加一个新的节点都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应地具体操作。
- 破坏封装。访问者模式要求访问者对象访问并调用每一个节点对象的操作,这隐含了一个对所有几点对象的要求:特曼必须暴露一些自己的操作和内部状态。
本质
预留通路,回调实现。
实例
ps:仅代表个人思路
【问题】
扩展客户管理的功能(CRM)。
- 个人客户、企业客户。
- 提出服务深情,客户价值分析,客户对产品偏好分析等。
【代码】
public abstract class CRMClient {
abstract public void accept(CRMOperation crm);
}
public class PersonCRMClient extends CRMClient {
@Override
public void accept(CRMOperation crm) {
crm.personOpera(this);
}
}
public class CompanyCRMClient extends CRMClient {
@Override
public void accept(CRMOperation crm) {
crm.companyOpera(this);
}
}
public abstract class CRMOperation {
abstract public void personOpera(PersonCRMClient pc);
abstract public void companyOpera(CompanyCRMClient cc);
}
public class ServiceCRMOperation extends CRMOperation {
@Override
public void personOpera(PersonCRMClient pc) {
System.out.println("对个人客户"+pc+"提出服务申请。");
}
@Override
public void companyOpera(CompanyCRMClient cc) {
System.out.println("对企业客户"+cc+"提出服务申请。");
}
}
public class ValueCRMOperation extends CRMOperation {
@Override
public void personOpera(PersonCRMClient pc) {
System.out.println("对个人客户"+pc+"进行客户价值分析。");
}
@Override
public void companyOpera(CompanyCRMClient cc) {
System.out.println("对企业客户"+cc+"进行客户价值分析。");
}
}
public class PreferenceCRMOperation extends CRMOperation {
@Override
public void personOpera(PersonCRMClient pc) {
System.out.println("对个人客户"+pc+"进行客户对产品偏好分析。");
}
@Override
public void companyOpera(CompanyCRMClient cc) {
System.out.println("对企业客户"+cc+"进行客户对产品偏好分析。");
}
}
public class CRMStructure {
private List<CRMClient> list=new ArrayList<CRMClient>();
public void attach(CRMClient crm) {
list.add(crm);
}
public void detach(CRMClient crm) {
list.remove(crm);
}
public void accept(CRMOperation co) {
for(CRMClient crm:list) {
crm.accept(co);
}
}
}
public class Client {
public static void main(String[] args) {
CRMStructure crm=new CRMStructure();
crm.attach(new PersonCRMClient());
crm.attach(new CompanyCRMClient());
CRMOperation c1=new ServiceCRMOperation();
CRMOperation c2=new ValueCRMOperation();
CRMOperation c3=new PreferenceCRMOperation();
crm.accept(c1);
crm.accept(c2);
crm.accept(c3);
}
}
【UML】