惯例, 先帖参考[1][2][3][4].
记得第一次看<Design Patterns>的时候, 被类与类之间的引用调用设计弄得不是一般的糊涂. 很多情况下是你引用我, 我引用他, 他调用你... 最后总结出一条正确且无聊的规律: 给我你的引用, 我就能完全控制你...
2年后...
设计的一个原则就是封装变化. 所以为了应对访问方式的多样化, 我们封装了对对象的访问方式(Object.Accept(Visitor)). 很多问题, 都有它的本质, 即便是一类问题的解决方案, 也许也只是解决范围内的冰山一角. [2],[3]都对Visitor做了较深的挖掘, 可以看出, visitor pattern某种程度上来说仅仅是Double dispatch的子集.
为了封装变化, 现在的主宰方式当然是类化, 调用虚方法; 子类化, 重载虚方法, 注入新类. 典型情况下, 如果我们的设计比较符合Liskov Substitution Principle,
那么类似下面的方法形式我们是可以接受的, 88%的问题也可以通过这种方式解决.
{
iPos.StepOne();
this .FollowOne();
iPos.StepTwo();
this .FollowTwo();
iPos.StepLastStepNine();
}
这样的设计虽然通过每一个子方法的合适重载版本, 可以得到符合设计的整体逻辑, 但是, 这样其实存在调用框架的限制, 类似于Template pattern. 只有在抽象足够得体的情况下(框架稳定), 才能很好的满足诡异的变化.
假如存在这样的需求, 当X情况下, 我们需要参数和本类的一种协作, 在XX情况下, 需要另外一种完全不同的协作方式... 即, 在不同的前提情况, 我们不能取得一致的抽象框架. 这时候, 我们可以考虑使用double dispatch:
{
void StepOne();
void StepTwo();
void StepTen();
void Execute(MainCls);
}
class ProcessOne : IProcess
{
public void Execute(MainCls mCls)
{
StepOne();
mCls.FollowTwo();
}
}
class ProcessTwo : IProcess
{
public void Execute(MainCls mCls)
{
mCls.FollowOne();
StepTen();
}
}
class MainCls
{
public void DoSome(IProcess iPos)
{
iPos.Execute( this );
}
}
Double dispatch, 顾名, 我们把调用作了再一次的dispatch, dispatch到了参数方法调用中.
显而易见, 这样的做法最大的缺点就是参数类耦合了调用类. 在iPos.Execute(this)中, 对于iPos的Execute方法, 当然可以应用重载机制, 但Execute(this)对于方法参数的匹配应用的却是方法复写机制, 所以想提取MainCls到接口参数来解除依赖, 需要多考虑是否合理. 这样做法的最大优点就是调用类完全不依赖于参数类型, 方便调用方法的逻辑变化.
另, [2]很值得一读.
-----------------------------------------
Ref:
[1], http://msdn.microsoft.com/en-us/magazine/cc546578.aspx
[2], http://www.cnblogs.com/idior/articles/325036.html
[3], http://blog.csdn.net/chaocai2004/archive/2009/02/19/3911753.aspx
[4], http://www.garyshort.org/blog/archive/2008/02/11/double-dispatch-pattern.aspx