继承与组合的区别
继承
若在逻辑上B是一种A(is a kind of),则允许B继承A的功能,它们之间就是is-A的关系。如男人(Man)是人(Human)的一种,女人(Woman)是人(Human)的一种。那么类Man可以从类Human派生,类Woman也可以从类Human派生。示例程序如下:
class Human
{
...
};
class Man : public Human
{
...
};
class Woman : public Human
{
...
};
在UML中,继承关系被称为泛化(Generalization)。类Man和类Woman与类Human的UML关系图可描述如下:
继承在逻辑上看起来比较简单,但在实际应用上可能遭遇意外。比如在面向对象中,著名的“鸵鸟不是鸟”和“圆不是椭圆”的问题。这样的问题说明了程序设计和现实世界存在逻辑差异。从生物学的角度,鸵鸟(Ostrich)是鸟(bird)的一种,既然是is-A的关系,类COstrich应该可以从类CBird派生。但是鸵鸟不会飞,但从CBird那里继承了接口函数fly,如下所示:
class CBird
{
public:
virtual void fly() {}
};
class COstrich
{
public:
...
};
“圆不是椭圆“同样存在类似的问题,圆从椭圆类继承了无用的长短轴数据成员,所以更加严格的继承应该是:若在逻辑上B是A的一种,并且A的所有功能和属性对B都有意义,则允许B继承A的所有功能和属性。
类继承允许我们根据自己的实现来覆盖重写父类的实现细节,父类的实现对于子类是可见的,所以我们一般称之为白盒复用。继承易于修改或扩展那些被复用的实现,但是这种白盒复用却容易破坏封装性。因为这会将父类的实现细节暴露给子类。
组合
若在逻辑上A是B的”一部分“(a part of),则不允许B继承A的功能,而是要用A和其他东西组合出来B,它们之间就是 Has-A关系。例如眼(Eye),鼻(nose),口(mouth),耳(ear)是头(head)的一部分,所以类Head应该由Eye,Nose,Mouth,Ear组合而成,而不是派生而成。
class Eye
{
public:
void Look(void);
};
class Nose
{
public:
void Smell(void);
};
class Mouth
{
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;
};
如果允许Head从Eye,Nose,Mouth,Ear派生而成,那么Head将自动具有Look,Smell,Eat,Listen这些功能。
//错误的设计
class Head : public Eye,public Nose, public Mouth,public Ear {};
实心菱形代表了一种坚固的关系,被包含类的生命周期受包含类控制,被包含类会随着包含类创建而创建,消亡而消亡。组合属于黑盒复用,被包含对象的内部细节对外是不可见的,所以它的封装性相对较好,实现上相互依赖比较小,并且可以通过获取其他具有相同类型的对象引用或指针,在运行期间动态的定义组合。缺点就是使系统中的对象过多。
综上所述,is-A关系用继承表示,Has-A关系用组合表示,优先使用对象组合,而不是类继承。
转https://blog.csdn.net/K346K346/article/details/55045295