GoF:“定义一个能够在一个对象结构中对于所有元素执行的操作。访问者让你可以定义一个新的操作,而不必更改到被操作元素的类接口”(一般我打出GoF就说明我不是很理解)
简单来说,访问者是一个考虑了所有“被访问者”可能会需要的接口(功能),被访问者需要放入访问者的它所需的接口中进行处理
处理的问题如下:
假设有Enemy敌人类 和 Solider士兵类 它们均继承于Character基类。
现在需求是统计每一个敌人的防御力求平均值 和 统计每一个士兵的攻击力求平均值 至于求出的数据放哪可以先不管。
一般做法是在士兵类Solider 和 敌人类Enemy中 分别写一个方法来进行处理对应的问题,这样就会改动了2个原有类,违背了开闭原则,而且可能还会提高耦合度。假设又有一个类似的需求出现,那还需要再继续增加原有类接口进行处理!最终原有类会越来越庞大变得难以维护。
用访问者模式就可以避免这种情况,对照这个需求创建一个访问者类VistorA,内部有两个方法如下:
VisitSolider(List<Solider> soliderList){ ... }
VisitEnemy(List<Enemy> enemyList){ ... }
调用时: VistorA va = new VistorA();
va.VisitSolider( GetSoliderList() );//GetSoliderList()获取所有士兵
va.VisitEnemy( GetEnemyList() );//GetEnemyList()获取所有敌人
假设又有一个类似的需求,需要访问原有类Enemy或Solider ,而且也同样是访问所有士兵和敌人,此时你就可以再创一个VistorB来处理这个需求,而VistorA和VistorB是十分类似的,为此我们可以写一个接口Vistor来进行抽象化,每一个需求VistorX访问者都要继承于Vistor,让实现和调用分离开来,从而降低耦合(这就是面向接口编程思想)
变为如下:
Vistor vistor = new VistorA()
vistor.VistorSolider(soliderList)
vistor.VistorEnemy(enemyList)
假设要转为 新需求VistorB的方式,代码只需改动上面的VistorA变为VistorB就OK了!因为A和B都是实现Vistor接口的方法,其方法名和方法所需参数是一样的,这就保证了我们只需要改动一个地方就OK了,当然上面这种举例还是太浅了,等到具体遇到项目问题时,就会理解面向接口的好处(或者说是面向抽象的好处)
2020年6月22日02:16:56更新 比上方的例子稍微复杂点的例子,可以对比着2种的访问模式,都是适用于不同需求而言的,没有根本上的利弊之分,我认为这些操作都是具体和抽象之间的转化而已,下面例子是被访问者的方法调用交给访问者进行。
public interface IVistor
{
void VisitA(BeVisitA beVisit);
void VisitB(BeVisitB beVisit);
}
public class FirstVistor : IVistor
{
//(4) [依赖具体调用] 执行被访问者A的具体方法
public void VisitA(BeVisitA beVisit)
{
beVisit.Run();
}
public void VisitB(BeVisitB beVisit)
{
beVisit.Sleep();
}
}
public class SecondVistor : IVistor
{
//其实IVistor可以只写一个接口Visit(IBeVisit beVisit),
//不过要写if判断和强转对应类型进行调用具体类方法。如 if(beVisit is BeVisitA){ ((BeVisitA)beVisit).Walk(); }
//这会消耗一定性能
public void VisitA(BeVisitA beVisit)
{
beVisit.Walk();
}
public void VisitB(BeVisitB beVisit)
{
beVisit.Look();
}
}
public interface IBeVisit
{
void VisitedByWho(IVistor vistor);
}
public class BeVisitA : IBeVisit
{
//实现接口方法,外部遍历调用IBeVisit接口时进入到此,从抽象变成具体调用,然后再变成抽象调用
public void VisitedByWho(IVistor vistor)
{
//(3)[抽象调用] 访问者VisitA接口方法,传递被访问者this,由具体访问者进行访问this
vistor.VisitA(this);
//假设我直接在这里调用Run() 或 Walk()方法,访问者的意义就不存在了
//访问者意义在于,选择执行哪个具体方法,比如FirstVistor选择执行Run,SecondVistor选择执行Walk,
//当然你也可以弄个ThirdVistor执行Run()和Walk()。
//很明显,如果你没有vistor辅助,你需要新增BeVisitA接口,破坏内部封闭原则。
}
public void Run()
{
//...
}
public void Walk()
{
//...
}
}
public class BeVisitB : IBeVisit
{
public void VisitedByWho(IVistor vistor)
{
vistor.VisitB(this);
}
public void Sleep()
{
//...
}
public void Look()
{
//...
}
}
void Main()
{
List<IBeVisit> list = new List<IBeVisit>();//(1)依赖倒置[抽象]
list.Add(new BeVisitA());
list.Add(new BeVisitB());
IVistor vistor = new FirstVistor();
foreach(var v in list)
{
//(2)[抽象调用]调用IBeVisit接口方法VisitedByWho,由Visitor访问者决定如何进行调用v的方法
v.VisitedByWho(vistor);
}
}
上方例子注释(1)、(2)、(3)、(4)按顺序看,整个调用流程,抽象->(具体)->抽象->具体, 第二步的具体是指从调用IBeVisit接口方法VisitedByWho,程序转到具体的IBeVisit接口实现类进行,其他的应该都很好懂。
思考:将旧例子Enemy和Solider魔改,但依然保持一部分会如何,需求改变为访问所有角色(敌人和士兵)调用它们的共同角色接口DoSomething()。看看会出现啥问题。
public interface ICharacter
{
void DoSomething();
}
public class Enemy : ICharacter
{
public void DoSomething()
{
Fly();
}
public void Fly()
{
//...
}
}
public class Solider : ICharacter
{
public void DoSomething()
{
Run();
}
public void Run()
{
//...
}
}
public interface IVistor
{
void VisitAll(List<ICharacter> list);
}
public class FirstVistor : IVistor
{
public void VisitAll(List<ICharacter> list)
{
foreach(var v in list)
{
v.DoSomething();
//写到这里时我突然发现,是的,这个接口无意义,vistor没有起到任何作用
//除非这个访问者还进行了其他特别的操作 如:调用v.DoSomething之后,假设DoSomething
//有返回值bool, 判断bool是否为false,为false则继续执行其他操作。如此一来就有了变化
}
}
}
void Main()
{
}