庙堂与江湖(一)
发表于 2006-04-06 05:06 PM 作者: liuxinyu
Inverse inheritance
庙堂与江湖
通常的继承,都是子类继承父类,但是下面这个代码是什么呢?
如果T是具体的apple, banana, orange,而MyClass是Fruit,这段代码的含义就是——哦,继承反转,他能带来什么呢?
北京在四月虽然是春天,却是干旱与多风。所以北方有“春雨贵如油”的民谚。然而却有很多的人川流不息于其中,而不知远千里之外江南,却是“和风细雨”,尤其是4月初,更是“清明时节雨纷纷”的景象。古代长期以来都是把北方的一些城市,如北京,长安,汴梁作为“庙堂”,而把江南一带称为“江湖”。
通常是居庙堂者,车马衣裘,趾高气扬;而谪江湖者,去国怀乡,满目萧然。千年来直至今日,无数公卿大夫、学者文人未能免诉,连李白也一旦蒙天子征召入京就:“仰天大笑出门去”,更何况一般的凡人呢。然而却也有人,能够“居庙堂之高,则忧其民;处江湖之远,则忧其君”,例如北宋的范仲淹就是“先天下之忧而忧,后天下之乐而乐”。
范仲淹的“江湖与庙堂”观,是反其道而行之的。“反其道而行之”能让人耳目一新,发现新的天地。长期以来,对于面向对象世界中的继承和多态,一直以来的“道”是子类继承父类,抽象衍生具体。如果“反其道而行之”会是什么呢?如果“继承反转”,会不会天下大乱呢?
先看一个典型的问题及其传统解法:比如希望建立一套数字系统,不管整数,浮点数,统一纳入,那么传统的做法基本如此:
使用这套系统时,借助运行时多态和虚函数,大体如下:
如果Number是抽象的最高层,也就是在政治中心“庙堂”,而各个具体的Integer和Float则是在基层的“江湖”。现在“反其道而行之”,看看如何使用“继承反转”。
首先是“居庙堂之高,则忧其民”,Number愿意放下高高的架子,甘心从“子民”继承:
人在“江湖”的子民,不用做任何改动,“君为轻,民为重,社稷次之”,Number甘心在所有的ConcreteNumber下面“为人民服务”,调用方法如下:
输出结果一样。但是感觉似乎不对,这是“多态”么?虽然是*.display()形式的调用,可是num1和num2分属不同的型别Number<Integer>和Number<Float>,这和下面的代码有区别么?
Integer num1(3);
Float num2(3.14);
num1.display();
num2.display();
如何能够界定是否是“多态”?所谓“多态”(Polymorphism)是指具体事物对于抽象事物表现出各自独特的行为,使用者对于抽象事物的操作,最终结果依照其实际代表的具体事物而不同。
皇帝甚至不知道某个地方官的姓名,只知道一旨政令发出,大家会遵照执行,置于你是雷厉风行的执行,贪赃枉法的执行,还是再次交待给下级官员去执行则不去管了。
所以从居庙堂的“皇帝”角度看,多态就是对于抽象事物的一致使用,例如:
同样,下面的用法也是对抽象事物的一致使用:
所以多态是语义上的概念,而实际使用中出现了运行时多态和编译时多态这两种不同的表现。
现在的问题是,“继承反转”有什么用,相比较传统做法,优点在哪里?甚至似乎根本不用声明一个Number<T>而直接可以把Integer和Float传给printNumber<T>。继承反转不是画蛇添足么?这里仍然采用提出问题——解决问题的思路来回答这些疑问。比如现在需要让所有的数字类都支持copy构造函数和等号赋值,并且支持基本的逻辑大小运算。如果采用传统做法,需要的工作是:为Number接口增加copy ctor、等号,各个大于小于等于逻辑运算虚函数声明,然后依次给自上而下给Integer和Float等concrete class实现上述接口,工作繁冗而重复。但是一旦有了“处江湖之远,则忧其君”的Number<T>,这些工作就可以自动化完成:
由于Number反转继承自所有的ConcreteNumber,所以为Number书写的所有方法,也就自然添加给了Number<ConcreteNumber>,编译器会重复这些枯燥的重复工作,调用时如下:
如果能够把传统方法和反转继承结合起来,就会出现更加有意思的结果,也就是在原来Number-ConcreteNumber的基础上增加一个“忧民忧国”的范仲淹NumberBase,整个继承体系如下:
Number
|
|-----------------|-----------------|
Integer... Float... Short
| | |
NumberBase<Integer> NumberBase<Float> NumberBase<Short>
这样,可以在“庙堂”把Number定义为抽象接口,规定抽象的方法(protocal),然后在“江湖”由最底层的NumberBase<T>驱动编译器自动化生成大量重复的代码。并且还有一个额外的收获——“范仲淹”可以充当一个类型安全的工厂。
首先Number, Integer, Float的定义和实现和传统方法一致,然后定义“范仲淹”:
“范仲淹”反转继承自所有的ConcreteNumber,因此也继承自Number。此后就可以利用“范仲淹”这个类型安全工厂来生产Number了:
现在看看“范仲淹”(NumberBase)如何帮助“庙堂之君”(Number)驱动编译器为“江湖之民”(Float和Integer等)生成赋值,逻辑运算符的代码:
首先“庙堂之君”规定要有如下接口(protocal):
然后Integer, Float等什么都不做,而由“范仲淹”来驱动编译器自动生成:
此后,“政令一出,则四方咸应”:
同样的方法,如果“庙堂之君”想加入算术运算加减乘除的话,不必担心要改动Float和Integer的一行,“江湖之民”的负担被减小到0,而工作由“先天下之忧而忧,后天下之乐而乐”的反转继承者完成。
庙堂与江湖
通常的继承,都是子类继承父类,但是下面这个代码是什么呢?
代码:
template<class T>
MyClass: public T{
...
}; 北京在四月虽然是春天,却是干旱与多风。所以北方有“春雨贵如油”的民谚。然而却有很多的人川流不息于其中,而不知远千里之外江南,却是“和风细雨”,尤其是4月初,更是“清明时节雨纷纷”的景象。古代长期以来都是把北方的一些城市,如北京,长安,汴梁作为“庙堂”,而把江南一带称为“江湖”。
通常是居庙堂者,车马衣裘,趾高气扬;而谪江湖者,去国怀乡,满目萧然。千年来直至今日,无数公卿大夫、学者文人未能免诉,连李白也一旦蒙天子征召入京就:“仰天大笑出门去”,更何况一般的凡人呢。然而却也有人,能够“居庙堂之高,则忧其民;处江湖之远,则忧其君”,例如北宋的范仲淹就是“先天下之忧而忧,后天下之乐而乐”。
范仲淹的“江湖与庙堂”观,是反其道而行之的。“反其道而行之”能让人耳目一新,发现新的天地。长期以来,对于面向对象世界中的继承和多态,一直以来的“道”是子类继承父类,抽象衍生具体。如果“反其道而行之”会是什么呢?如果“继承反转”,会不会天下大乱呢?
先看一个典型的问题及其传统解法:比如希望建立一套数字系统,不管整数,浮点数,统一纳入,那么传统的做法基本如此:
代码:
class Number{
public:
virtual void display()=0;
virtual ~Number(){}
};
class Integer: public Number{
public:
Integer(int v):value(v){}
void display(){ std::cout<<"an integer "<<value<<"\n"; }
protected:
int value;
};
class Float: public Number{
public:
Float(float v):value(v){}
void display(){ std::cout<<"a float "<<value<<"\n"; }
protected:
float value;
}; 代码:
int main(int argc, char* argv[]){
Number* num1 = new Integer(3);
Number* num2 = new Float(3.14);
num1->display();
num2->display();
delete num1;
delete num2;
} 首先是“居庙堂之高,则忧其民”,Number愿意放下高高的架子,甘心从“子民”继承:
代码:
template<class ConcreteNumber>
class Number: public ConcreteNumber{
public:
template<class T>
Number(T v):ConcreteNumber(v){}
}; 代码:
int main(int argc, char* argv[]){
Number<Integer> num1(3);
Number<Float> num2(3.14);
num1.display();
num2.display();
} Integer num1(3);
Float num2(3.14);
num1.display();
num2.display();
如何能够界定是否是“多态”?所谓“多态”(Polymorphism)是指具体事物对于抽象事物表现出各自独特的行为,使用者对于抽象事物的操作,最终结果依照其实际代表的具体事物而不同。
皇帝甚至不知道某个地方官的姓名,只知道一旨政令发出,大家会遵照执行,置于你是雷厉风行的执行,贪赃枉法的执行,还是再次交待给下级官员去执行则不去管了。
所以从居庙堂的“皇帝”角度看,多态就是对于抽象事物的一致使用,例如:
代码:
void printNumber(Number* n){
n->display();
}
Number* num1 = new Integer(3);
Number* num2 = new Float((float)3.14);
printNumber(num1);
printNumber(num2); 代码:
template<class NumberType>
void printNumber(NumberType n){
n.display();
}
Number<Integer> num1(3);
Number<Float> num2(3.14);
printNumber(num1);
printNumber(num2); 现在的问题是,“继承反转”有什么用,相比较传统做法,优点在哪里?甚至似乎根本不用声明一个Number<T>而直接可以把Integer和Float传给printNumber<T>。继承反转不是画蛇添足么?这里仍然采用提出问题——解决问题的思路来回答这些疑问。比如现在需要让所有的数字类都支持copy构造函数和等号赋值,并且支持基本的逻辑大小运算。如果采用传统做法,需要的工作是:为Number接口增加copy ctor、等号,各个大于小于等于逻辑运算虚函数声明,然后依次给自上而下给Integer和Float等concrete class实现上述接口,工作繁冗而重复。但是一旦有了“处江湖之远,则忧其君”的Number<T>,这些工作就可以自动化完成:
代码:
template<class ConcreteNumber>
class Number: public ConcreteNumber{
public:
template<class T>
Number(T v):ConcreteNumber(v){}
template<class T>
Number(const Number<T>& ref){ value = ref.value; }
Number& operator=(const Number& ref) { value = ref.value; }
bool operator==(const Number& ref) { return value == ref.value; }
bool operator< (const Number& ref) { return value < ref.value; }
bool operator> (const Number& ref) { return value > ref.value; }
}; 代码:
Number<Integer> num1(3);
Number<Float> num2(3.14);
Number<Integer> num3 = num1;
Number<Float> num4(1.414);
if(num2>num4)
num3.display(); 如果能够把传统方法和反转继承结合起来,就会出现更加有意思的结果,也就是在原来Number-ConcreteNumber的基础上增加一个“忧民忧国”的范仲淹NumberBase,整个继承体系如下:
Number
|
|-----------------|-----------------|
Integer... Float... Short
| | |
NumberBase<Integer> NumberBase<Float> NumberBase<Short>
这样,可以在“庙堂”把Number定义为抽象接口,规定抽象的方法(protocal),然后在“江湖”由最底层的NumberBase<T>驱动编译器自动化生成大量重复的代码。并且还有一个额外的收获——“范仲淹”可以充当一个类型安全的工厂。
首先Number, Integer, Float的定义和实现和传统方法一致,然后定义“范仲淹”:
代码:
template<class ConcreteNumber>
class NumberBase: public ConcreteNumber{
public:
template<class T>
NumberBase(T v):ConcreteNumber(v){}
template<class T>
static NumberBase* Create(T v) { return new NumberBase(v); }
}; 代码:
std::vector<Number*> coll;
coll.push_back( NumberBase<Integer>::Create(3) );
coll.push_back( NumberBase<Float>::Create(3.14) );
coll.push_back( NumberBase<Float>::Create(1.414) );
for(std::vector<Number*>::iterator it=coll.begin();
it!=coll.end(); ++it)
(*it)->display(); 现在看看“范仲淹”(NumberBase)如何帮助“庙堂之君”(Number)驱动编译器为“江湖之民”(Float和Integer等)生成赋值,逻辑运算符的代码:
首先“庙堂之君”规定要有如下接口(protocal):
代码:
class Number{
public:
virtual Number& operator=(const Number& ref)=0;
virtual bool operator==(const Number& ref)=0;
virtual bool operator< (const Number& ref)=0;
bool operator> (const Number& ref) {
return !((*this)==ref || (*this)<ref);
}
virtual void display() = 0;
virtual ~Number(){}
}; 代码:
template<class ConcreteNumber>
class NumberBase: public ConcreteNumber{
public:
//略…
NumberBase& operator=(const Number& ref){
if(const NumberBase* p=
dynamic_cast<const NumberBase*>(&ref)){
value = p->value;
return *this;
}
throw std::runtime_error("type mismatch");
}
bool operator==(const Number& ref){
if(const NumberBase* p=
dynamic_cast<const NumberBase*>(&ref)){
return value == p->value;
}
throw std::runtime_error("type mismatch");
}
bool operator< (const Number& ref) {
if(const NumberBase* p=
dynamic_cast<const NumberBase*>(&ref)){
return value < p->value;
}
throw std::runtime_error("type mismatch");
}
}; 代码:
std::vector<Number*> coll;
for(int i=0; i<10; ++i)
coll.push_back(NumberBase<Integer>::Create(i));
for(int i=0; i<5; ++i)
(*coll)=(*coll[i+5]);
for(int i=0; i<9; ++i)
if((*coll)>(*coll[i+1])){
std::cout<<"at "<<i<<" ";
coll->display();
} 评论总数 4
评论
| | 好文。 大侠可否介绍一下继承反转相对于普通方法的缺点? |
| 发表于 2008-01-21 05:36 PM 作者: docu (docu1107@163.com) |
| | 嗯,今天刚刚发完了后面的部分,相对于普通方法。采用静态多态的反转继承的优点是: +类型安全,编译器负责检查类型,而传统方法,用户手里只有一个指向父类的指针。 +编译期绑定,所以不需要RTTI,速度快。 +多型别虚函数,这个是创通方法不能提供的 缺点是: +对象占用空间大 +缺乏类似编译器对纯虚函数是否被实现的检查,如果某个纯虚函数没有被子类实现,采用传统方法的话,编译器就会指出,但是反转继承没有直接简单的手法。 |
发表于 2008-01-21 05:37 PM 作者: liuxinyu |
| | 这不就是wtl里面用到的静态多态嘛 |
| 发表于 2008-01-21 05:38 PM 作者: step_by_stp@263.net |
| | 正如Herb Sutter和Andrei Alexandrescue在C++ Coding Standard里面指出的,运行时多态和静态多态都有各自的特点,只有把二者结合起来,发挥各自的优势,才能获得更高层次的抽象和代码复用。从这一点上来讲,以前许多不得不手工做的事情,现在有办法可以交给编译器去做。 |
发表于 2008-01-21 05:41 PM 作者: liuxinyu |
发表评论 |
作者为 liuxinyu 的最新文章
- beautiful code第20章和第29章翻译 (2007-11-06)
- 延迟求值和无穷(6) (2007-08-15)
- 延迟求值和无穷(5) (2007-08-15)
- 延迟求值和无穷(4) (2007-08-15)
- 延迟求值和无穷(3) (2007-08-15)




