组合
如果鸟是可以飞的,那么鸵鸟是鸟么?鸵鸟如何继承鸟类?[美国某著名分析软件公司2005年面试题]
解析:如果所有鸟都能飞,那鸵鸟就不是鸟!回答这种问题时,不要相信自己的直觉!将直觉和合适的继承联系起来还需要一段时间。
根据题干可以得知:鸟是可以飞的。也就是说,当鸟飞行时,它的高度是大于0的。鸵鸟是鸟类(生物学上)的一种。但它的飞行高度为0(鸵鸟不能飞)。
不要把可替代性和子集相混淆。即使鸵鸟集是鸟集的一个子集(每个驼鸟集都在鸟集内),但并不意味着鸵鸟的行为能够代替鸟的行为。可替代性与行为有关,与子集没有关系。当评价一个潜在的继承关系时,重要的因素是可替代的行为,而不是子集。
答案:如果一定要让鸵鸟来继承鸟类,可以采取组合的办法,把鸟类中的可以被鸵鸟继承的函数挑选出来,这样鸵鸟就不是“a kind of”鸟了,而是“has some kind of”鸟的属性而已。代码如下:
#include<bits/stdc++.h>
using namespace std;
class bird
{
public:
void eat()
{
cout<<"bird is eating"<<endl;
}
void sleep()
{
cout<<"bird is sleeping"<<endl;
}
void fly();
};
class ostrich
{
public:
void eat()
{
smallBird.eat();
}
void sleep()
{
smallBird.sleep();
}
private:
bird smallBird;
};
int main()
{
ostrich xiaoq;
xiaoq.eat();
xiaoq.sleep();
return 0;
}
再举一个例子
例如眼(Eye)、鼻(Nose)、口(Mouth)、耳(Ear)是头(Head)的一部分,所以类Head 应该由类Eye、Nose、Mouth、Ear 组合而成,不是派生而成。示例程序如下:
#include<bits/stdc++.h>
using namespace std;
class Eye
{
public:
void Look();
};
class Nose
{
public:
void Smell();
};
class Mouth
{
public:
void Eat();
};
class Ear
{
public:
void Listen();
};
class Head
{
public:
void Look()
{
m_eye.Look();
}
void Smell()
{
m_nose.Smell();
}
void Eat()
{
m_mouth.Eat();
}
void Listen()
{
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 这些功能。
实心菱形代表了一种坚固的关系,被包含类的生命周期受包含类控制,被包含类会随着包含类创建而创建,消亡而消亡。组合属于黑盒复用,被包含对象的内部细节对外是不可见的,所以它的封装性相对较好,实现上相互依赖比较小,并且可以通过获取其它具有相同类型的对象引用或指针,在运行期间动态的定义组合。而缺点就是致使系统中的对象过多。
继承
类之间的关系
has-A,uses-A 和 is-A
has-A 包含关系,用以描述一个类由多个“部件类”构成。实现has-A关系用类成员表示,即一个类中的数据成员是另一种已经定义的类。
uses-A 一个类部分地使用另一个类。通过类之间成员函数的相互联系,定义友员或对象参数传递实现。
is-A 机制称为“继承”。关系具有传递性,不具有对称性。
继承关系举例
万事万物中皆有继承,是重要的现象
两个案例:1)植物继承图;2)程序员继承图
派生类的定义
C++中的继承方式(public、private、protected)会影响子类的对外访问属性。
子类拥有父类的所有成员变量和成员函数
子类可以拥有父类没有的方法和属性
子类就是一种特殊的父类
子类对象可以当作父类对象使用
派生类的访问控制
派生类继承了基类的全部成员变量和成员方法(除了构造和析构之外的成员方法),但是这些成员的访问属性,在派生过程中是可以调整的。
不同的继承方式会改变继承成员的访问属性
1)C++中的继承方式会影响子类的对外访问属性
public继承:父类成员在子类中保持原有访问级别
private继承:父类成员在子类中变为private成员
protected继承:父类中public成员会变成protected
父类中protected成员仍然为protected
父类中private成员仍然为private
2)private成员在子类中依然存在,但是却无法访问到。不论种方式继承基类,派生类都不能直接使用基类的私有成员。
3)C++中子类对外访问属性表
| 父类成员访问级别 | |||
继 承 方 式 |
| public | proteced | private |
public | public | proteced | private | |
proteced | proteced | proteced | private | |
private | private | private | Private |
“三看”原则
C++中的继承方式(public、private、protected)会影响子类的对外访问属性
判断某一句话,能否被访问
1)看调用语句,这句话写在子类的内部、外部
2)看子类如何从父类继承(public、private、protected)
3)看父类中的访问级别(public、private、protected)
访问设置原则
1、需要被外界访问的成员直接设置为public
2、只能在当前类中访问的成员设置为private
3、只能在当前类和子类中访问的成员设置为protected,protected成员的访问权限介于public和private之间。
结论:一般情况下class B : public A
例子:
。如男人(Man)是人(Human)的一种,女人(Woman)是人的一种。那么类Man 可以从类Human 派生,类Woman也可以从类Human 派生。示例程序如下:
class Human
{
…
};
class Man : public Human
{
…
};
class Woman : public Man
{
…
};
“圆不是椭圆”同样存在类似的问题,圆从椭圆类继承了无用的长短轴数据成员。所以更加严格的继承应该是:若在逻辑上B是A的一种,并且A的所有功能和属性对B都有意义,则允许B继承A的所有功能和属性。
类继承允许我们根据自己的实现来覆盖重写父类的实现细节,父类的实现对于子类是可见的,所以我们一般称之为白盒复用。继承易于修改或扩展那些被复用的实现,但它这种白盒复用却容易破坏封装性。因为这会将父类的实现细节暴露给子类。
不良继承出现的根本原因在于对继承的理解不够深刻,错把直觉中的“是一种(Is-A)”当成了学术中的“子类型(subtype)”概念。在继承体系中,派生类对象是可以取代基类对象的。而在椭圆和圆的问题上,椭圆类中的成员函数setSize(x,y)违背了这个可置换性,即Liskov替换原则。
所有不良继承都可以归结为“圆不是椭圆”这一著名具有代表性的问题上。在不良继承中,基类总会有一些额外能力,而派生类却无法满足它。这些额外的能力通常表现为一个或多个成员函数提供的功能。要解决这一问题,要么使基类弱化,要么消除继承关系,需要根据具体情形来选择。
组合与继承的选择
组合和继承是允许在新建的类中放入子对象,组合是显示的这样做的,而继承是隐式的做。二者是之间有何区别?或者怎样在二者之间做出选择呢?
组合一般是将现有的类型作为新类型底层实现的一部分来加以复用,在一个类中引用另一个类,而继承是拥有了父类的非私有方法。其中“is-a (是一个)”的关系是用继承来表达的,而“has-a(有一个)”的关系则是用组合来表达的。
组合关系和继承关系相比,前者的最主要优势是不会破坏封装,在软件开发阶段,组合关系虽然不会比继承关系减少编码量,但是到了软件维护阶段,由于组合关系使系统具有较好的松耦合性,因此使得系统更加容易维护。组合关系的缺点是比继承关系要创建更多的对象。从软件构架来说,组合,耦合度比继承弱,继承是对父类方法和数据成员的兼收并蓄,而组合,可以有选择得使用某一种方法。
到底是该用组合还是继承,一个最清晰的判断方法就是问一问自己是否需要从新类向基类进行向上转型,需要的话就用继承,不需要的话就用组合方式。
标签:组合,继承,子类,成员,void,父类,public From: https://blog.51cto.com/u_15952369/6036919