面向对象编程的几个关键概念继承、多态、组合

时间:2021-07-30 17:23:06
 

一、继承、接口与多态的相关问题:

1、 继承的作用?好处?坏处?

继承:通过继承实现代码复用。Java中所有的类都是通过直接或间接地继程java.lang.Object类得到的。继承而得到的类称为子类,被继承的类称为父类。子类不能继承父类中访问权限为private的成员变量和方法。子类可以重写父类的方法,及命名与父类同名的成员变量。但Java不支持多重继承,即一个类从多个超类派生的能力。

优点:a因为大部分是继承而来的,实现代码重用,减少代码书写量;

b很容易修改和扩展已有的实现

缺点:a打破了封装,因为基类向子类暴露了实现细节

b白盒重用,因为基类的内部细节通常对子类是可见的

c当父类的实现改变时可能要相应的对子类做出改变

d不能在运行时改变由父类继承来的实现

2、 接口的好处?坏处?

优点:帮助Java语言实现一个类似于多继承的功能.但是实现的多继承功能不会使代码中的类之间出现网状关系,而是比较清楚的树状关系,类似于家谱的感觉。

缺点:如果向一个java接口加入一个新的方法时,所有实现这个接口的类都得编写具体的实现。

4、 什么是重载?什么是重写?

重载:

a方法重载是让类以统一的方式处理不同类型数据的一种手段。多个同名函数同时存在,具有不同的参数个数/类型。重载Overloading是一个类中多态性的一种表现。

b Java的方法重载,就是在类中可以创建多个方法,它们具有相同的名字,但具有不同的参数和不同的定义。调用方法时通过传递给它们的不同参数个数和参数类型来决定具体使用哪个方法, 这就是多态性。

c重载的时候,方法名要一样,但是参数类型和个数不一样,返回值类型可以相同也可以不相同。无法以返回型别作为重载函数的区分标准。

重写:

a父类与子类之间的多态性,对父类的函数进行重新定义。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。在Java中,子类可继承父类中的方法,而不需要重新编写相同的方法。但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写。方法重写又称方法覆盖。

b若子类中的方法与父类中的某一方法具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法。如需父类中原有的方法,可使用super关键字,该关键字引用了当前类的父类。

c子类函数的访问修饰权限不能少于父类的;

5、 什么是组合?

组合: a通过创建一个由其他对象组合的对象来获得新功能的重用方法

b新功能的获得是通过调用组合对象的功能实现的

c有时又叫聚合

优点:a被包含对象通过包含他们的类来访问

b黑盒重用,因为被包含对象的内部细节是不可见的

c很好的封装

d每个类专注于一个任务

e通过获得和被包含对象的类型相同的对象引用,可以在运行时动态定义组合的方式

缺点:a结果系统可能会包含更多的对象

b为了使组合时可以使用不同的对象,必须小心的定义接口

类是面象对象编程(OOP)的基本概念。OOP将数据和函数封装到称为类的玩意中。
继承是软件复用的一种形式,实现这种形式的方法是从现有的类建立新类,新类继承了现有类的方法和属性,同时新类又可以定义自己的方法和属性。软件复用缩自短了开发时间。继承的魅力在于能够添加基类没有的特点从而对基类进行改进。
例如:在VC开发环境中有类库(MFC,ATL),他们就是已经编好了的类,在开发软件时可以对类库中的类进行扩展、改进,这在实际的开发中经常用到。做法就是以类库中的类为基类构造自己的类。说白了,继承是面向对象的基本特征,其最大的用处是可以软件复用。

多态是比较高级的特性,另一个高级特性是模板,这两个在构造复杂系统时才用得上,如MFC类库中大量运用了多态特性,而ATL将模板运用得淋漓尽致。然而我们在学习C++之初对多态、模板并不重视,而将大量精力放在了指针、继承、重载等之上。当然这是合理的,因为指针、继承等是最基本的知识。

在学习C++之初对多态没有正真理解,直到学了MFC后,真正理解了多态有如些大的用途,C++是如此有魅力,而学了ATL之后才明白原来模板可以这么用。

多态分为模板的静态多态表现和类继承的动态绑定两种.
C++编译器在编译的时候,要确定每个对象调用的函数的地址,这称为早期绑定。与之对应的就是晚绑定。动态多态就是其中一种。
动态多态主要体现在虚函数的调用。在C++中,虚函数的调用使用的是动态绑定,也有人说是晚绑定、运行时绑定,也就是在程序运行时才决定调用的函数。这是通过类的虚表实现的(了解即可)

请注意,C++中多态的实现只在通过基类指针调用虚函数时才会实现。

关于多态下面举个例子就明白了:
如我们定义了一个动物类,动物都有一个共同的行为就是呼吸,那么把呼吸定义在基类中(这很好理解)。
在设计软件时我们可能会基于程序的灵活性或算法特殊要求而使用基类指针来调用派生类的方法。
比如基于上述类我们开发一个展示动物呼吸的程序,定义了几百种动物并实现了每种动物的breathe方法,使用者通过鼠标点击决定看哪一种动物的呼吸方式。

设计时考虑到,使用者并不是每次都想看完这几百种动物的呼吸,所以我们没有必要预先产生各种动物的对象,而只要在使用者点了某种动物后使用new操作符动态创建该对象即可,这样可以简化程序设计和节省内存空间。
class animal
{
...
virtual void breathe() //注意这是虑函数
{
cout<<"animal breathe"<<endl;
}
};

class fish:public animal
{
...
void breathe()
{
cout<<"fish bubble"<<endl;
}
};

class cat:public animal
{
...
void breathe()
{
cout<<"cat bubble"<<endl;
}
};


void main()
{
animal* pAn;
switch(用户选择)
{
case fish:
pAn = new fish;
break;
case cat:
pAn = new cat;
break;
....
}
pAn->breathe();
delete pAn;
}
}

当用户选鱼时输出:fish breathe
当用户选猫时输出:cat breathe
但是如果你将基类的breathe函数virtual申明除掉,那么不管你怎么选都是输出

animal breathe
C++中多态的实现只在通过基类指针调用虚函数时才会实现

你问:“如题,以及什么时候必须用以上两种呢”
我认为没有必须,一个问题可用多种方法实现,在使用多态能更好的设计算法时就使用多态,在需要软件复用时就用继承。但要记住一点:C++中使用多态一定要用基类指针,并且一定要在基类中定义虚函数。