返回   cpper编程论坛 > Blog > liuxinyu
注册账号 论坛帮助 会员列表 日历事件 搜索 今日新帖 标记版面已读

为这篇文章评分

庙堂与江湖(一)

发表于 2006-04-06 05:06 PM 作者: liuxinyu
Inverse inheritance

庙堂与江湖

通常的继承,都是子类继承父类,但是下面这个代码是什么呢?

代码:
template<class T>
MyClass: public T{
   ... 
};
如果T是具体的apple, banana, orange,而MyClass是Fruit,这段代码的含义就是——哦,继承反转,他能带来什么呢?

北京在四月虽然是春天,却是干旱与多风。所以北方有“春雨贵如油”的民谚。然而却有很多的人川流不息于其中,而不知远千里之外江南,却是“和风细雨”,尤其是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是抽象的最高层,也就是在政治中心“庙堂”,而各个具体的Integer和Float则是在基层的“江湖”。现在“反其道而行之”,看看如何使用“继承反转”。

首先是“居庙堂之高,则忧其民”,Number愿意放下高高的架子,甘心从“子民”继承:
代码:
template<class ConcreteNumber>
class Number: public ConcreteNumber{
public:
     template<class T>
     Number(T v):ConcreteNumber(v){}
};
人在“江湖”的子民,不用做任何改动,“君为轻,民为重,社稷次之”,Number甘心在所有的ConcreteNumber下面“为人民服务”,调用方法如下:
代码:
int main(int argc, char* argv[]){
     Number<Integer> num1(3);
     Number<Float>   num2(3.14);
     num1.display();
     num2.display();
}
输出结果一样。但是感觉似乎不对,这是“多态”么?虽然是*.display()形式的调用,可是num1和num2分属不同的型别Number<Integer>和Number<Float>,这和下面的代码有区别么?
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反转继承自所有的ConcreteNumber,所以为Number书写的所有方法,也就自然添加给了Number<ConcreteNumber>,编译器会重复这些枯燥的重复工作,调用时如下:
代码:
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); }
};
“范仲淹”反转继承自所有的ConcreteNumber,因此也继承自Number。此后就可以利用“范仲淹”这个类型安全工厂来生产Number了:
代码:
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(){}
};
然后Integer, Float等什么都不做,而由“范仲淹”来驱动编译器自动生成:
代码:
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();
     }
同样的方法,如果“庙堂之君”想加入算术运算加减乘除的话,不必担心要改动Float和Integer的一行,“江湖之民”的负担被减小到0,而工作由“先天下之忧而忧,后天下之乐而乐”的反转继承者完成。
评论 4 Email文章
评论总数 4

评论

旧
好文。
大侠可否介绍一下继承反转相对于普通方法的缺点?
发表于 2008-01-21 05:36 PM 作者: docu (docu1107@163.com)
旧
liuxinyu 的头像
嗯,今天刚刚发完了后面的部分,相对于普通方法。采用静态多态的反转继承的优点是:
+类型安全,编译器负责检查类型,而传统方法,用户手里只有一个指向父类的指针。
+编译期绑定,所以不需要RTTI,速度快。
+多型别虚函数,这个是创通方法不能提供的
缺点是:
+对象占用空间大
+缺乏类似编译器对纯虚函数是否被实现的检查,如果某个纯虚函数没有被子类实现,采用传统方法的话,编译器就会指出,但是反转继承没有直接简单的手法。
发表于 2008-01-21 05:37 PM 作者: liuxinyu liuxinyu 当前离线
旧
这不就是wtl里面用到的静态多态嘛
发表于 2008-01-21 05:38 PM 作者: step_by_stp@263.net
旧
liuxinyu 的头像
正如Herb Sutter和Andrei Alexandrescue在C++ Coding Standard里面指出的,运行时多态和静态多态都有各自的特点,只有把二者结合起来,发挥各自的优势,才能获得更高层次的抽象和代码复用。从这一点上来讲,以前许多不得不手工做的事情,现在有办法可以交给编译器去做。
发表于 2008-01-21 05:41 PM 作者: liuxinyu liuxinyu 当前离线
发表评论 发表评论
作者为 liuxinyu 的最新文章

所有时间均为格林尼治时间 +9。现在的时间是 12:28 PM


Powered by vBulletin® 版本 3.7.0
版权所有 ©2000 - 2008,Jelsoft Enterprises Ltd.
(C) Copy Right All Right Reserved 2001 - 2007

Search Engine Friendly URLs by vBSEO 3.1.0