5. 软件重用的方式
本文最后更新于 2024年1月27日 下午
软件重用的方式
这是浙江大学翁恺老师的公开课,《面向对象设计C++》
视频地址:
https://www.bilibili.com/video/BV1yQ4y1A7ts/?spm_id_from=333.337.search-card.all.click&vd_source=3074f6f6ab43a114c5af8727fa4f7255
本节对应视频14-16部分。
对象组合
面向对象的三大特性是封装、继承和多态性。继承和组合是面向对象对软件重用的实现的关键方式。
组合(composition)是通过已有的对象组合起来来创造出新的对象,可以用“has-a”的语言来描述新对象和原来对象的关系:比如已经有“轮胎”和“引擎”两个对象,就可以组合起来创造出新的对象“车”。
面向对象的五条基本原则中就指出“一个对象的内部是其他对象”。
从C++代码实现的角度来看,组合指的是在设计一个类的时候,这个类的成员变量可以是其他类的对象。
组合的实现方式有两种,一种叫Fully,另一种叫By
reference。Fully是指一个对象在另一个对象内部,即成员变量是其他对象。 By
reference是指一个对象通过指针引用另一个对象,成员变量是指向其他对象的指针。根据类的设计决定使用哪种方法。
比如一个雇员的信息包括了:名字、住址、岗位和直属领导,这里直属领导就是另一个雇员。
下面展示了一个Fully的例子:
1
2
3
4
5
6
7
8
9
10
11
12class Person {...};
class Currency {...};
class SavingAccount {
public:
SavingAccount(const char* name, const char* address, int cents);
~SavingAccount();
void print();
private:
Person m_saver; //Fully
Currency m_balance; // Fully
};SavingAccount
中包括了Currency
和Person
两个类,SavingAccount
的构造函数需要在初始化时调用Currency
和Person
的构造函数:
1
2
3
4
5
6SavingAccount(const char* name, const char* address, int cents): m_saver(name,address),m_balance(0,cents) {...}
void SavingsAccount::print() {
m_saver.print();
m_balance.print();
// 假设Person 和 Currency都提供了print方
}
如果类当中有成员变量是其他类的对象,它就应该在初始化列表中被初始化。
继承
继承(inheritance)将现有的类作为基础,通过改造得到一个新的类,它可以使得类之间共享成员数据、成员函数和接口(成员函数public的部分)的设计。
继承是用一个类来得到一个新的类的设计方法,新的类是原来的类的超集(superset),比如下图可以说“student”继承了“person”。也就是说,继承的类必须是原来的类的扩充,比如“student”所拥有的描述/数据/功能要比“person”更多。
在继承方法中,新类和老的类的关系可以描述为“is-A”,原来的类称为父类(parent
class),从这个类继承的新类称为这个类的子类(child
class)或者派生类(derived class)。
继承的写法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24#include<iostream>
using namespace std;
class A {
public:
A():i(0) { cout << "A::A()" << endl;}
~A() { cout << "A::~A()" << endl;}
void print() {cout<<"A::print()"<< i << endl;}
void set(int ii) { i = ii;}
private:
int i;
};
class B : public A { //class B继承了A的设计,public是必须的
};
int main()
{
B b; //类B继承了类A中全部的内容,因此对象b具有类A的对象的全部内容
b.set(10); //B也具有A的set函数
b.print();
return 0;
};b
被创建的过程也会调用类A
的构造函数;此外,b
也会拥有set
和print
这两个函数,这表明子类继承了父类包括父类的构造函数、析构函数、公共部分和私有部分等全部内容。
如果这时候对类B
进行一些修改:
1
2
3
4
5
6
7
8
9
10
11
12
13
14...
class B : public A { //class B继承了A的设计,public是必须的
public:
void f() {set(20); print();} //B中新增加的部分
};
int main()
{
B b; //类B继承了类A中全部的内容,因此对象b具有类A的对象的全部内容
b.set(10); //B也具有A的set函数
b.print();
b.f();
return 0;
};
编译时,父类的构造函数先被调用,再构造子类。析构的时候,子类先被析构,父类后被析构。
protected
protected
是一种访问权限,它允许父类和子类允许使用protected
的内容,但是程序的其他部分(包括主函数main
)都不能调用protected
的内容。
比如: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25#include<iostream>
using namespace std;
class A {
public:
A():i(0) { cout << "A::A()" << endl;}
~A() { cout << "A::~A()" << endl;}
void print() {cout<<"A::print()"<< i << endl;}
protected:
void set(int ii) { i = ii;} //set()是受保护的
private:
int i;
};
class B : public A {
public:
void f() {set(20); print();}
};
int main()
{
B b;
b.set(10); //此时main函数不能够访问set()函数,该程序会出错
b.print();
b.f();
return 0;
};
1
2
3
4
5$ vi a.cpp
$ g++ a.cpp
>> a.cpp: In function 'int main()':
>> a.cpp:11:error:'void A::set(int)' is protected
>> a.cpp:29:error: within this context
总结在类的设计中的几类权限:private
用于保护父类的所有的数据,public
用于父类向子类和程序的其他部分提供服务,protected
的接口用于让父类向子类能够操作数据。