c++组合跟继承的区别及混合使用时构造函数的执行顺序

类的继承与组合:

         对象(Object)是类(Class)的一个实例(Instance)。如果将对象比作房子,那么类就是房子的设计图纸。所以面向对象设计的重点是类的设计,而不是对象的设计。对于 C++程序而言,设计孤立的类是比较容易的,难的是正确设计基类及其派生类。
本章仅仅论述“继承”(Inheritance)和“组合”(Composition)的概念。注意,当前面向对象技术的应用热点是COM 和CORBA,这些内容超出了C++教材的范畴,请阅读COM 和CORBA 相关论著。

继承:

        如果 A 是基类,B 是A 的派生类,那么B 将继承A 的数据和函数。例如:


class A
{
public:
    void Func1(void);
    void Func2(void);
};
class B : public A
{
public:
    void Func3(void);
    void Func4(void);
};
int main()
{
    B b;
    b.Func1(); // B 从A 继承了函数Func1
    b.Func2(); // B 从A 继承了函数Func2
    b.Func3();
    b.Func4();
    return 0
}

        这个简单的示例程序说明了一个事实:C++的“继承”特性可以提高程序的可复用性。正因为“继承”太有用、太容易用,才要防止乱用“继承”。我们应当给“继承”立一些使用规则。

规则1:

        如果类A 和类B 毫不相关,不可以为了使B 的功能更多些而让B继承A 的功能和属性。不要觉得“不吃白不吃”,让一个好端端的健壮青年无缘无故地吃人参补身体。
 

规则2:

        若在逻辑上B 是A 的“一种”(a kind of ),则允许B 继承A 的功能和属性。例如男人(Man)是人(Human)的一种,男孩(Boy)是男人的一种。那么类Man 可以从类Human 派生,类Boy 可以从类Man 派生。

class Human
{
    …
};
class Man : public Human
{
    …
};
class Boy : public Man
{
    …
};

注意事项:
        规则2看起来很简单,但是实际应用时可能会有意外,继承的概念在程序世界与现实世界并不完全相同。
例如从生物学角度讲,鸵鸟(Ostrich)是鸟(Bird)的一种,按理说类Ostrich 应该可以从类Bird 派生。但是鸵鸟不能飞,那么Ostrich::Fly 是什么东西?

class Bird
{
public:
    virtual void Fly(void);
    …
};
class Ostrich : public Bird
{
    …
};

        例如从数学角度讲,圆(Circle)是一种特殊的椭圆(Ellipse),按理说类Circle 应该可以从类Ellipse 派生。但是椭圆有长轴和短轴,如果圆继承了椭圆的长轴和短轴,岂非画蛇添足?所以更加严格的继承规则应当是:若在逻辑上B 是A 的“一种”,并且A 的所有功能和属性对B 而言都有意义,则允许B 继承A 的功能和属性。

继承:

        若在逻辑上A 是B 的“一部分”(a part of),则不允许B 从A 派生,而是要用A 和其它东西组合出B。
例如眼(Eye)、鼻(Nose)、口(Mouth)、耳(Ear)是头(Head)的一部分,所以类Head 应该由类Eye、Nose、Mouth、Ear 组合而成,不是派生而成

class Eye
{
public:
    void Look(void);
};

class Nose
{
public:
    void Smell(void);
};

class Mouth
{
public:
    void Eat(void);
};

class Ear
{
public:
    void Listen(void);
};

// 正确的设计,虽然代码冗长。
class Head
{
public:
    void Look(void) { m_eye.Look(); }
    void Smell(void) { m_nose.Smell(); }
    void Eat(void) { m_mouth.Eat(); }
    void Listen(void) { m_ear.Listen(); }
private:
    Eye m_eye;
    Nose m_nose;
    Mouth m_mouth;
    Ear m_ear;
};
示例由Eye、Nose、Mouth、Ear 组合而成

如果允许Head 从Eye、Nose、Mouth、Ear 派生而成,那么Head 将自动具有Look、Smell、Eat、Listen 这些功能。以下示例十分简短并且运行正确,但是这种设计方法却是不对的

// 功能正确并且代码简洁,但是设计方法不对。
class Head : public Eye, public Nose, public Mouth, public Ear
{
};

一只公鸡使劲地追打一只刚下了蛋的母鸡,你知道为什么吗?因为母鸡下了鸭蛋。很多程序员经不起“继承”的诱惑而犯下设计错误。“运行正确”的程序不见得是高质量的程序,此处就是一个例证

继承跟组合的区别:

继承的优点和缺点
优点:

        容易进行新的实现,因为其大多数可继承而来。
        易于修改或扩展那些被复用的实现。
缺点:
      破坏了封装性,因为这会将父类的实现细节暴露给子类。
      “白盒”复用,因为父类的内部细节对于子类而言通常是可见的。
      当父类的实现更改时,子类也不得不会随之更改。
      从父类继承来的实现将不能在运行期间进行改变。
组合的优点和缺点
优点:

      容器类仅能通过被包含对象的接口来对其进行访问。
      “黑盒”复用,因为被包含对象的内部细节对外是不可见。
      封装性好。
      实现上的相互依赖性比较小。(被包含对象与容器对象之间的依赖关系比较少)
      每一个类只专注于一项任务。
      通过获取指向其它的具有相同类型的对象引用,可以在运行期间动态地定义(对象的)组合。
缺点:
      导致系统中的对象过多。
      为了能将多个不同的对象作为组合块(composition block)来使用,必须仔细地对接口进行定义。

两者的选择

       is-a关系用继承表达,has-a关系用组合表达

      继承体现的是一种专门化的概念而组合则是一种组装的概念

      另外确定是组合还是继承,最清楚的方法之一就是询问是否需要新类向上映射,也就是说当我们想重用原类型作为新类型的内部实现的话,我们最好自己组合,如果我们不仅想重用内部实现而且还想重用接口的话,那就用继承。

     法则:优先使用(对象)组合,而非(类)继承

当继承组合混合使用时构造函数执行顺序:

class a
{
public:
    a(){
	printf("aaaaaaaaa\n");
    }
};

class b
{
public:
    b(){
	printf("bbbbbbbbb\n");
    }
};

class c
{
public:
    c(){
        printf("ccccccccc\n");
    }
};

class d : public a
{
public:
    d(){
	printf("ddddddddd\n");
    }
private:
    b xx;
    c ddd;
};


int main()
{   
    d ddd;
    return 0;
}

执行的结果:

aaaaaaaaa
bbbbbbbbb
cccccccccc
ddddddddd

顺序:基类的构造函数->组合类的构造函数->子类的构造函数

       构造从类层次的最根处开始,在每一层中,首先调用基类的构造函数,然后调用成员对象的构造函数。析构则严格按照与构造相反的次序执行,该次序是唯一的,否则编译器将无法自动执行析构过程。一个有趣的现象是,成员对象初始化的次序完全不受它们在初始化表中次序的影响,只由成员对象在类中声明的次序决定。这是因为类的声明是唯一的,而类的构造函数可以有多个,因此会有多个不同次序的初始化表。如果成员对象按照初始化表的次序进行构造,这将导致析构函数无法得到唯一的逆序

 

你可能感兴趣的:(C++)