分派是一个过程,这个过程用来确定应用场景中具体使用的是哪个方法函数,比如在代码中可以有n个重名的甚至重参数的方法,那么具体使用的是哪个函数呢,这个对函数的确认过程即分派。
那么分派的准则是什么呢?是宗量,一个方法所属的对象叫做方法的接收者,方法的接收者与方法的参量统称做方法的宗量。在此基础上,分派分为单分派和多分派两种。
单分派根据一个宗量类型对方法进行选择,多分派(双分派)根据多个(两个)宗量对方法进行选择。
而目前的高级语言大都是单分派语言(c++/java/c#),具体分派方式根据阶段又分为两种:
一种是静态分派,即方法的分派发生在代码的编译阶段,对应的实现技术为函数重载,这时编译器只根据重名方法的不同参数来判断具体使用哪个方法。也就是我们所说的编译时多态。
一种是动态分派,即方法的分派发生在代码的执行阶段,对应的实现技术为函数重写,这时程序里只根据函数具体接收者来判断具体使用哪个类的方法。也就是我们所说的运行时多态。动态分派是根据运行时具体实际参数的类型来判断调用哪个函数的。这里多说一嘴,其实函数的具体接收者本质也是一个参数,参数的名称为this。
也就是说我们平时使用面向对象的继承、派生、多态特性即可满足我们大部分的工作需要,但是双分派的作用也是相当大的,你给函数参数传入的是基类指针(c++),双分派能根据实际指向的派生类的类型来选择具体使用哪个函数,然而c++、java等高级语言都没有实现,我们来看看静态分派,动态分派,双分派(c++虽然没有实现,但是我们也把期望的代码写了出来)的具体应用:
//Problem.h
class CProblem
{
public:
CProblem(void){}
virtual ~CProblem(void){}
void comm_func();
void comm_func(int a);
};
//Problem.cpp
#include
#include "Problem.h"
void CProblem::comm_func()
{
printf("problem common function 1 in \n");
}
void CProblem::comm_func(int a)
{
printf("problem common function 2 in \n");
}
//ConcreteProblem.h
#include "Problem.h"
class ConcreteProblem:public CProblem
{
public:
ConcreteProblem(void){}
~ConcreteProblem(void){}
void extern_func();
};
//ConcreteProblem.cpp
#include "ConcreteProblem.h"
#include
void ConcreteProblem::extern_func()
{
printf("extern function in\n");
}
//Processor.h
class CProblem;
class ConcreteProblem;
class CProcessor
{
public:
CProcessor(void){}
virtual ~CProcessor(void){}
virtual void dealProblem(CProblem *prob);
virtual void dealProblem(ConcreteProblem *prob);
};
//Processor.cpp
#include "Processor.h"
#include "ConcreteProblem.h"
void CProcessor::dealProblem(CProblem *prob)
{
printf("普通人员处理普通问题\n");
prob->comm_func();
}
void CProcessor::dealProblem(ConcreteProblem *prob)
{
printf("普通人员处理特殊问题\n");
prob->comm_func();
prob->extern_func();
}
//ConcreteProcessor.h
#include "processor.h"
class ConcreteProcessor :public CProcessor
{
public:
ConcreteProcessor(void){}
~ConcreteProcessor(void){}
void dealProblem(CProblem *prob);
void dealProblem(ConcreteProblem *prob);
};
//ConcreteProcessor.cpp
#include
#include "ConcreteProcessor.h"
#include "ConcreteProblem.h"
void ConcreteProcessor::dealProblem(CProblem *prob)
{
printf("专职人员处理普通问题\n");
prob->comm_func();
}
void ConcreteProcessor::dealProblem(ConcreteProblem *prob)
{
printf("专职人员处理特殊问题\n");
prob->comm_func();
prob->extern_func();
}
//main.cpp
#include
#include
#include "ConcreteProblem.h"
#include "ConcreteProcessor.h"
int main(int argc ,char **argv)
{
CProblem *prob = NULL;
CProcessor *proc = NULL;
//静态分派
prob = new CProblem();
prob->comm_func();
prob->comm_func(10);
//动态单分派
proc = new CProcessor();
proc->dealProblem(prob);
//这里我想让专职人员处理特殊问题
//结果专职人员处理的还是普通的问题
//c++没有实现双分派
prob = new ConcreteProblem();
proc = new ConcreteProcessor();
proc->dealProblem(prob);
//派生类指针指向自己的对象
//这种情况下可以实现专职人员处理特殊问题
ConcreteProblem *concProb = new ConcreteProblem();
proc->dealProblem(concProb);
return 0;
}
上面的C++例子得到的结果:我们想要分派派生类的重名函数,只能使用动态单分派+静态单分派,双分派?不存在的。最后一种实现方式虽然能分派到派生类的重名函数,但是这明显需要应用场景知道具体派生类类型,这增加了应用场景与实际功能的耦合度,不方便扩展,如果可以实现双分派,应用场景只需要使用这些派生类统一的基类就能搞定,优势就在这。
虽然高级语言没有实现双分派,我们也可以变通的来实现双分派,实现方式就是动态单分派+静态单分派,我们将上面实现分派派生类重名函数的方法做一些改动:
ConcreteProblem *concProb = new ConcreteProblem();
proc->dealProblem(concProb);
既然使用静态单分派可以实现(重载)我们的目标,所差就是需要知道具体派生类,我们何不将上面的代码封装到ConcreteProblem 内部以接口 来实现,在调用proc->dealProblem(concProb)的时候传this指针就好了,具体如下:
//ConcreteProblem.cpp
#include "ConcreteProblem.h"
#include
void ConcreteProblem::extern_func()
{
printf("extern function in\n");
}
void ConcreteProblem::visit(CProcessor *proc)
{
proc->dealProblem(this);
printf("extern function in\n");
}
这样visit仍然是具体派生类的函数,对于应用场景来说还是需要知道具体派生类,那我们将visit函数做成虚函数不就得了,完整的代码如下:
//Problem.h
#include "Processor.h"
class CProblem
{
public:
CProblem(void){}
virtual ~CProblem(void){}
void comm_func();
void comm_func(int a);
virtual void visit(CProcessor *proc);
};
//Problem.cpp
#include
#include "Problem.h"
void CProblem::comm_func()
{
printf("problem common function 1 in \n");
}
void CProblem::comm_func(int a)
{
printf("problem common function 2 in \n");
}
void CProblem::visit(CProcessor *proc)
{
proc->dealProblem(this);
}
//ConcreteProblem.h
#include "Problem.h"
class ConcreteProblem:public CProblem
{
public:
ConcreteProblem(void){}
~ConcreteProblem(void){}
void extern_func();
void visit(CProcessor *proc);
};
//ConcreteProblem.cpp
#include "ConcreteProblem.h"
#include
void ConcreteProblem::extern_func()
{
printf("extern function in\n");
}
void ConcreteProblem::visit(CProcessor *proc)
{
proc->dealProblem(this);
}
//main.cpp
#include
#include
#include "ConcreteProblem.h"
#include "ConcreteProcessor.h"
int main(int argc ,char **argv)
{
CProblem *prob = NULL;
CProcessor *proc = NULL;
//静态分派
prob = new CProblem();
prob->comm_func();
prob->comm_func(10);
//动态单分派
proc = new CProcessor();
proc->dealProblem(prob);
//这里我想让专职人员处理特殊问题
prob = new ConcreteProblem();
proc = new ConcreteProcessor();
proc->dealProblem(prob);
//派生类指针指向自己的对象
//这种情况下可以实现专职人员处理特殊问题
ConcreteProblem *concProb = new ConcreteProblem();
proc->dealProblem(concProb);
//自行实现的双分派,分派根据两个具体宗量来判断函数的调用
prob = new ConcreteProblem();
proc = new ConcreteProcessor();
prob->visit(proc);
return 0;
}
这种方式实现后,应用场景就不用考虑两个宗量的具体类型了,更加符合迪米特法则;