前置文章: 设计模式的原则
其他设计模式:用心理解设计模式
设计模式相关代码已统一放至 我的 Github
一、定义
行为型模式之一。
Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.
(表示要对对象结构的元素执行的操作,访问者模式能够让你定义一个新的操作,而无需改变它所操作的元素所在的类。)
二、结构解析
访问者模式的一般结构有五种角色: 对象结构、抽象元素、具体元素、抽象访问者、具体访问者。
对象结构(ObjectStructure):一个稳定的数据模型(要被访问操作的元素所在的类),包含一些元素(元素个数总是不变),并实现了对这些元素进行操作的方法,在这些操作方法中,完成了具体元素对访问者的接受(接受后即反调,所以也可以说是 “完成访问者对元素的访问”)。
抽象元素(Element):定义一个用于接受访问者的接口,并要求子类在该接口的实现里,最终反调访问者对元素进行访问操作的接口,达成访问者访问元素的目的。
具体元素(ConcreteElement):实现抽象元素定义的接口。
抽象访问者(Visitor):定义访问者对每个具体元素的访问操作接口。
具体访问者(ConcreteVisitor):实现抽象访问者定义的接口。
三、评价
访问者模式,将数据模型的操作实现放到了访问者中,使数据模型变得稳定不变、对模型中元素的操作变得易于扩展。
它的核心思想请参考 消除程序中的 if else(二),实际就是使用了 策略模式,即,访问者自带访问策略。
对比使用访问者模式前后的访问过程:
使用前,访问者调用对象结构中用于访问元素的方法,在该方法内,根据访问者的不同,对元素进行不同的访问操作。(这种方式,访问操作实现在对象结构中,对象结构耦合了各个具体访问者,导致增加访问者就要修改对象结构,违背开闭原则)
使用后,访问者自备 “对元素的操作方法” 去访问对象结构中的元素,被访问元素接受访问者,并反调访问者自备的“对元素的操作方法”。(这种方式,访问操作实现在访问者中,对象结构依赖的是抽象访问者,增加具体访问者不用修改对象结构,满足开闭原则)
四、实现
using UnityEngine;
namespace Visitor
{
//对象结构
public class ObjectStructure
{
private ConcreteElementX elementX;
private ConcreteElementY elementY;
public ObjectStructure()
{
elementX = new ConcreteElementX();
elementY = new ConcreteElementY();
}
public void VisitElementX(Visitor visitor)
{
//原来,在每次增加新的访问者时,都必须修改该方法,增加一个If和相应的访问操作逻辑。
//if (visitor is ConcreteVisitorA)
//{
// Debug.Log("ConcreteVisitorA 访问 ElementX");
//}
//else if (visitor is ConcreteVisitorB)
//{
// Debug.Log("ConcreteVisitorB 访问 ElementY");
//}
// 现在总是这一句,保证了ObjectStructur类不因增加新的访问者(新的访问者对应新的操作)而被修改!
// 访问者对元素的访问操作逻辑,被放到了访问者中去扩展。
elementX.Accept(visitor);
}
public void VisitElementY(Visitor visitor)
{
elementY.Accept(visitor);
}
}
//抽象元素
public abstract class Element
{
public abstract void Accept(Visitor visitor);
}
//具体元素X
public class ConcreteElementX : Element
{
public override void Accept(Visitor visitor)
{
visitor.VisitElementX(this);
}
}
//具体元素Y
public class ConcreteElementY : Element
{
public override void Accept(Visitor visitor)
{
visitor.VisitElementY(this);
}
}
//抽象访问者
public abstract class Visitor
{
//访问元素的接口,这里可以有两种写法。
//这两种写法都不能解决扩展“元素”时对“访问者”的修改问题(事实上该设计模式,就是在Element稳定的基础上进行的)。
//推荐第二种(养成习惯,面向抽象编程和接口编程,尽可能将 已确定/不变 部分放在抽象类中)
//写法1、在抽象 Visitor中 定义一个唯一访问接口,然后在每个ConcreteVisitor的实现中对每个元素进行区分对待。
//public abstract void VisitElement(Element element);
//写法2、在抽象 Visitor中 针对不同元素,定义不同的访问接口 VisitElementX、VisitElementY。
public abstract void VisitElementX(ConcreteElementX element);
public abstract void VisitElementY(ConcreteElementY element);
}
//具体访问者A
public class ConcreteVisitorA : Visitor
{
public override void VisitElementX(ConcreteElementX element)
{
Debug.Log("ConcreteVisitorA 访问 ElementX");
}
public override void VisitElementY(ConcreteElementY element)
{
Debug.Log("ConcreteVisitorA 访问 ElementY");
}
}
//具体访问者B
public class ConcreteVisitorB : Visitor
{
public override void VisitElementX(ConcreteElementX element)
{
Debug.Log("ConcreteVisitorB 访问 ElementX");
}
public override void VisitElementY(ConcreteElementY element)
{
Debug.Log("ConcreteVisitorB 访问 ElementY");
}
}
//客户
public class Client
{
static public void Main()
{
ObjectStructure objectStructur = new ObjectStructure();
ConcreteVisitorA concreteVisitorA = new ConcreteVisitorA();
ConcreteVisitorB concreteVisitorB = new ConcreteVisitorB();
objectStructur.VisitElementX(concreteVisitorA);
objectStructur.VisitElementX(concreteVisitorB);
objectStructur.VisitElementY(concreteVisitorA);
objectStructur.VisitElementY(concreteVisitorB);
}
}
}