分派过程就是确定一个方法调用的过程,双分派就是根据运行时多个对象的类型确定方法调用的过程。
想象这样一个客户服务的场景,一般客户支持有一级支持和二级支持。一级支持一般解决比较简单的问题,如果问题解决不了,就会由二级支持来解决。
定义一般问题:
class Problem
{
public:
Problem(){}
virtual ~Problem(){}
};
定义特殊问题:
class SpecialProblem:public Problem
{
public:
SpecialProblem(){}
~SpecialProblem(){}
};
定义一级支持:
class Supporter
{
public:
Supporter(){}
virtual ~Supporter(){}
virtual void solve(Problem &p)
{
std::cout<
}
virtual void solve(SpecialProblem &sp)
{
std::cout<
}
};
定义资深支持:
class SeniorSupporter:public Supporter
{
public:
SeniorSupporter(){}
~SeniorSupporter(){}
void solve(Problem &p)
{
std::cout<
}
void solve(SpecialProblem &sp)
{
std::cout<
}
};
下面是测试类:
int main()
{
Problem *p=new Problem();
Problem *sp=new SpecialProblem();
Supporter *s=new SeniorSupporter();
s->solve(*p);
s->solve(*sp);
system("Pause");
return 1;
}
以下是预料中的错误运行结果:
反汇编代码如下:
如果自己动手编写类似的程序并设置断点调试的话就会发现:
s->solve(*p);
s->solve(*sp);
调用的都是void SeniorSupporter::solve(Problem &p){...}这个方法。
原因很简单:C++不支持Double Dispatch。但C++支持single Dispatch(单分派),在单分派语言中,到底由哪一种操作将来实现一个请求取决于两个方面:该请求的名和接受者的类型。而双分派意味着得到执行的操作决定于请求的种类和两个接受者的类型。solve是一个double dispatch操作,它的含义决定于两个类型:Supporter的类型和Problem的类型。
解决这个问题,就是想办法在运行时根据Problem和Supporter的具体类型进行分派。
在Problem中增加如下方法,在方法调用时将自身传入。
class Problem
{
public:
Problem(){}
virtual ~Problem(){}
virtual void solve(Supporter *s)
{
s->solve(*this);
}
//*virtual void solve(SeniorSupporter *sp)
//{
// sp->solve(*this);
//}
};
在SpecialProblem,增加如下方法,在方法调用时,将自身传入:
class SpecialProblem:public Problem
{
public:
SpecialProblem(){}
~SpecialProblem(){}
void solve(Supporter *s)
{
s->solve(*this);
}
//void solve(SeniorSupporter *s)
//{
// s->solve(*this);
//}
};
看看现在的测试代码:
int main()
{
Problem *p=new Problem();
Problem *sp=new SpecialProblem();
Supporter *s=new SeniorSupporter();
p->solve(s);
sp->solve(s);
system("Pause");
return 1;
}
以下是运行结果:
现在,通过调用:
p->solve(s);
sp->solve(s);
来实现两次动态分派,第一次是problem中solve方法的多态,第二次是supporter中solve方法的多态。
Visitor模式也使用了类似的方式:
Visitor模式中的Accept也是一个double dispatch操作,它的含义决定于两个类型:Visitor的类型和Element的类型。这是Visitor模式的关键所在:得到执行的操作不仅决定于Visitor的类型还决定于它访问的Element的类型。而采取的解决方案如上图,没有将操作静态的绑定在Element接口中,而是将其安放在一个Visitor中,并使用Accept在运行时进行绑定。
您的认真阅读我将不胜荣幸,您的指正修改我将万分感激。
版权所有,翻版不究,请注明出处即可。