C++有三大特性:封装、继承和多态。
之前提到过继承是为了实现代码的复用,如果子类继承了父类那么就会继承父类所有的数据成员及成员函数,在公有继承的时候保持is-a原则,即每个子类的对象也是父类对象。
因此引入了单继承、多继承以及菱形继承等方式。但是正是因为继承太有用、太容易用、很容易实现代码的复用性因此我们需要选择性的继承。
如果A类和B类毫无关系,我们不应该为了让B类多一个功能而去让B继承A。
如果在逻辑上B是A的一种,即B类的man也是A类的 Hunman的一种我们就可以让B类去继承A类。
#include<iostream>
#include<stdio.h>
using namespace std;
class Eye
{
public:
void Look(void)
{
cout << "hello" << endl;
}
};
class Nose
{
public:
void Smell(void)
{
cout << "Smell()" << endl;
}
};
class Mouth
{
public:
void Eat(void)
{
cout << "Eat()" << endl;
}
};
class Ear
{
public:
void Listen(void)
{
cout << "Listen" << endl;
}
};
class Head:public Eye,public Nose,public Mouth,public Ear
{
;
};
int main()
{
Head man;
man.Eat();
system("pause");
return 0;
}
这种采用继承的方法来实现Head类虽然代码简单并且运行结果也是正确的,但是它并不是一个高质量的程序,因为从上述例子我们可以看出Eye,Nose,Mouth,Ear这些类都是Head类的一部分(is -a-part-of)而不是(is -a-kind-of)的关系,他不能说成头部是眼睛、或者头部是鼻子,在逻辑上不符合在逻辑上B是A的一种。因此这种设计思想是有问题的。
我们可以通过组合来实现:
#include<iostream>
#include<stdio.h>
using namespace std;
class Eye
{
public:
void Look(void)
{
cout << "hello" << endl;
}
};
class Nose
{
public:
void Smell(void)
{
cout << "Nose()" << endl;
}
};
class Mouth
{
public:
void Eat(void)
{
cout << "Eat()" << endl;
}
};
class Ear
{
public:
void Listen(void)
{
cout << "Listen()" << endl;
}
};
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;
};
int main()
{
Head man;
man.Look();
system("pause");
return 0;
}
组合也是类的一种复用技术它遵循的就是如果A类是B类的一部分,则不要让B类去继承A类,而是采用组合的形式。
对比以上两个程序我们可以总结出组合和继承各自的优缺点:
组合
优点:不会破环封装性父类的任何变化不会引起子类的变化
组合运用复杂的设计他们的关系实在程序运行的时候才确定的可以支持动态的组合
整体类可以对局部类的接口进行封装,提供新的接口
缺点:整体类不能自动获得和局部类同样的接口,只有通过创建局部的对象去调用它
创建整体类的时候需要创建局部类的对象
继承
优点:子类继承了父类能自动获得父类的接口
创建子类对象的时候不用创建父类对象
缺点:破坏了封装,父类的改变必定引起子类的改变,子类缺乏独立性
支持功能上的扩展,但多重继承往往增加了系统结构的复杂度。
继承是在静态编译的时候就已经确定了关系,不支持动态继承。
因此优先考虑使用组合而不是继承
----------------------------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------------------------------------------
类的组合和继承一样,是软件重用的重要方式。组合和继承都是有效地利用已有类的资源。但二者的概念和用法不同。通过继承建立了派生类与基类的关系,它是一种 “是(is a)”的关系,如“白猫是猫”,“黑人是人”,派生类是基类的具体化实现,是基类中的一 种。通过组合建立了成员类与组合类(或称复合类)的关系,换句话说就是"有(has a)的关系".在功能上来看,它们都是实现系统功能重用,代码复用的最常用的有效的设计技巧,都是在设计模式中的基础结构。相信大家已了解的,类继承允许我们根据自己的实现来覆盖重写父类的实现细节,父类的实现对于子类是可见的,所以我们一般称之为白盒复用。对象持有(其实就是组合)要求建立一个号的接口,但是整体类和部分类之间不会去关心各自的实现细节,即它们之间的实现细节是不可见的,故成为黑盒复用。继承是在编译时刻静态定义的,即是静态复用,在编译后子类和父类的关系就已经确定了。而组合这是运用于复杂的设计,它们之间的关系是在运行时候才确定的,即在对对象没有创建运行前,整体类是不会知道自己将持有特定接口下的那个实现类。在扩展方面组合比集成更具有广泛性。继承中父类定义了子类的部分实现,而子类中又会重写这些实现,修改父类的实现,设计模式中认为这是一种破坏了父类的封装性的表现。这个结构导致结果是父类实现的任何变化,必然导致子类的改变。然而组合这不会出现这种现象。对象的组合还有一个优点就是有助于保持每个类被封装,并被集中在单个任务上(类设计的单一原则)。这样类的层次结构不会扩大,一般不会出现不可控的庞然大类。而累的继承就可能出来这些问题,所以一般编码规范都要求类的层次结构不要超过3层。组合是大型系统软件实现即插即用时的首选方式。最后还说一句,“优先使用对象组合,而不是继承”是面向对象设计的第二原则。在OOP中,组合和继承是扩展对象功能的两大利器,GoF在《设计模式》中指出OO设计的一大原则就是:优先使用对象组合,而不是类继承。但并不是说什么都设计都用组合,只是优先考虑组合,更不是说继承即使不好的设计,应该用组合,应为他们之间也有各自的优势。下面是他们之间的优缺点比比较表:
组 合 关 系 | 继 承 关 系 |
优点:不破坏封装,整体类与局部类之间松耦合,彼此相对独立 | 缺点:破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性 |
优点:具有较好的可扩展性 | 缺点:支持扩展,但是往往以增加系统结构的复杂度为代价 |
优点:支持动态组合。在运行时,整体对象可以选择不同类型的局部对象 | 缺点:不支持动态继承。在运行时,子类无法选择不同的父类 |
优点:整体类可以对局部类进行包装,封装局部类的接口,提供新的接口 | 缺点:子类不能改变父类的接口,但子类可以覆盖父类的接口 |
缺点:整体类不能自动获得和局部类同样的接口 | 优点:子类能自动继承父类的接口 |
缺点:创建整体类的对象时,需要创建所有局部类的对象 | 优点:创建子类的对象时,无须创建父类的对象 |