Unity【话大】设计模式之访问者模式
前言:笔者在最开始写程序的时候经常会遇到一种情况,例如更改一个字段、或者添加一种小功能,就要把原来写过的东西几乎废弃掉,或者更改大量以前写过的代码。又或者自己写的东西时间久了再去回顾,完全找不到到时为什么这么写的头绪,如果遇到了Bug更是无法快速定位在哪里小范围出现的问题。如果你也经常遇到这种问题,就说明你现阶段非常需要学习下设计模式了。
在网上经常说的设计模式有23种,也有一些更多的设计模式,无非也是从这些设计模式中变种而来。如果让笔者来形容什么是设计模式,我认为设计模式是:一种思想,一种模式,一种套路,一种解决问题的高效策略。
有说的不正确或者不准确的地方欢迎留言指正
有什么有趣的写作技巧或者想法欢迎大家给我留言,大家的帮助是我写下去最有效的动力
访问者模式(Visitor) 表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
今天笔者跟大家介绍一个相对复杂的设计模式---访问者模式,根据对访问者模式的定义我们知道两点信息,第一:对某个结构中的各元素操作,第二:在不改变个元素的类前提下操作。下面笔者用代码示例来说明如何对各元素操作,然后有如何在不改变类的前提下操作
先从最简单的说起,我们创建一个元素(Element)基类,然后根据这个基类创建一个持有基类的对象结构
Element以及对应的实现类
public abstract class Element
{
}
public class ConcreteElementA : Element
{
public void FuntionA()
{
Debug.Log("FuntionA");
}
}
public class ConcreteElementB : Element
{
public void FuntionB()
{
Debug.Log("FuntionB");
}
}
可以对各个元素操作的对象结构
public class ObjectStructure
{
private IList<Element> elements = new List<Element>();
public void Add(Element element)
{
elements.Add(element);
}
public void Remove(Element element)
{
elements.Remove(element);
}
public void Accept()
{
foreach (var item in elements)
{
if (item is ConcreteElementA)
{
ConcreteElementA temp = (ConcreteElementA)item;
temp.FuntionA();
}
else if ((item is ConcreteElementB))
{
ConcreteElementB temp = (ConcreteElementB)item;
temp.FuntionB();
}
}
}
}
调用
private void Start()
{
ObjectStructure objectStructure = new ObjectStructure();
ConcreteElementA concreteElementA = new ConcreteElementA();
ConcreteElementB concreteElementB = new ConcreteElementB();
objectStructure.Add(concreteElementA);
objectStructure.Add(concreteElementB);
objectStructure.Accept();
}
请看下图,是一个对指定元素列表遍历的示例,但是在每次遍历操作的时候都需要对元素进行强制转换,还有一点就是,如果在以后的需求中,我们需要在遍历时执行元素中不同的方法怎么办?难道要变成执行Accept1?Accept2?Accept3?等等,这显然违背了开闭原则。
所以我们需要再次的封装、再次的转移
现在我们开始对Accept函数部分进行改造,首先我们建造一个Visitor类,里面含有FuntionA与FuntionB 这样Visitor就变成一个函数的菜单,里面有各种等待挑选的函数
public class Visitor
{
public void VisitConcreteElementA(ConcreteElementA concreteElementA)
{
Debug.Log("FuntionA");
}
public void VisitConcreteElementB(ConcreteElementB concreteElementB)
{
Debug.Log("FuntionB");
}
}
ObjectStructure中的Accept则变成接受菜单的选项,在具体的Element实现类中访问这个菜单中需要的函数
public abstract class Element
{
public abstract void Accept(Visitor visitor);
}
public class ConcreteElementA : Element
{
public override void Accept(Visitor visitor)
{
visitor.VisitConcreteElementA(this);
}
}
public class ConcreteElementB : Element
{
public override void Accept(Visitor visitor)
{
visitor.VisitConcreteElementB(this);
}
}
public class ObjectStructure
{
private IList<Element> elements = new List<Element>();
public void Add(Element element)
{
elements.Add(element);
}
public void Remove(Element element)
{
elements.Remove(element);
}
public void Accept(Visitor visitor)
{
foreach (var item in elements)
{
item.Accept(visitor);
}
}
}
调用
private void Start()
{
ObjectStructure objectStructure = new ObjectStructure();
ConcreteElementA concreteElementA = new ConcreteElementA();
ConcreteElementB concreteElementB = new ConcreteElementB();
objectStructure.Add(concreteElementA);
objectStructure.Add(concreteElementB);
Visitor visitor = new Visitor();
objectStructure.Accept(visitor);
}
其实总体的思路可以这样理解,有两位客人(Element的实现类),分别向他们发送了一个需要访问的菜单(Visitor),然后这两位客人根据自身的需求在需要访问的菜单中挑选需要的函数。在遍历的时候我们只需要发放访问菜单就行,剩下的你们自己选,我不管。
既然选择访问的菜单有多种,那么我们继续进行抽象,也就是再次的封装和剥离
public class ConcreteVisitor1 : Visitor
{
public override void VisitConcreteElementA(ConcreteElementA concreteElementA)
{
Debug.LogFormat("{0}被{1}访问", concreteElementA.GetType().Name, this.GetType().Name);
}
public override void VisitConcreteElementB(ConcreteElementB concreteElementB)
{
Debug.LogFormat("{0}被{1}访问", concreteElementB.GetType().Name, this.GetType().Name);
}
}
public class ConcreteVisitor2 : Visitor
{
public override void VisitConcreteElementA(ConcreteElementA concreteElementA)
{
Debug.LogFormat("{0}被{1}访问", concreteElementA.GetType().Name, this.GetType().Name);
}
public override void VisitConcreteElementB(ConcreteElementB concreteElementB)
{
Debug.LogFormat("{0}被{1}访问", concreteElementB.GetType().Name, this.GetType().Name);
}
}
调用
private void Start()
{
ObjectStructure objectStructure = new ObjectStructure();
ConcreteElementA concreteElementA = new ConcreteElementA();
ConcreteElementB concreteElementB = new ConcreteElementB();
objectStructure.Add(concreteElementA);
objectStructure.Add(concreteElementB);
Visitor visitor1 = new ConcreteVisitor1();
Visitor visitor2 = new ConcreteVisitor2();
objectStructure.Accept(visitor1);
objectStructure.Accept(visitor2);
}
这样我们就会可以根据不同的访问菜单,实现不同的需求,ObjectStructure中的遍历方式不需要改变,有新的需求我们只需要添加不同的访问菜单(Visitor)就可以了
总结:访问者模式适用于数据结构相对稳定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由地演化。
访问者模式的目的是要把处理从数据结构分离出来。使得操作的增加变得容易,反之数据结构对象(element实现类)易于变化,经常有新的数据对象增加进来,就不适合使用访问者模式了。
优点
缺点
- 具体元素对访问者公布细节,违反了迪米特原则。
- 具体元素变更比较困难。
- 违反了依赖倒置原则,依赖了具体类,没有依赖抽象。