面向对象编程范式是具有对象概念的编程范式,它可能包含数据、特性、代码与方法。对象是指的是类 class
的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关联的数据。
面向对象编程范式最早是在 1966 年由 Ole Johan Dahl
和 Kriste Nygaard
在论文中总结归纳出来的,并在 1967 年发布的 Simula67
语言中,引入了类的概念和继承机制,它是面向对象程序设计语言的先驱。
Dahl
和 Nygaard
通过将函数调用堆栈 call stack frame
迁移到堆内存区域里,这样函数定义的本地变量就可以在函数返回之后继续存在。这个函数就成为了一个类 class
的构造函数,而它所定义的本地变量就是类的成员变量,构造函数定义的嵌套函数就成为了成员方法 method
。这样一来,我们就可以利用多态 polymorphism
来限制用户对函数指针的使用。面向对象编程即对程序的控制权的间接转移进行了限制和规范。
面向对象经过多年的发展,总结了封装 encapsulation
、继承 inheritance
、多态 polymorphism
三个特性,指的是面向对象编程是这三个特性的有机组合,或者任何一种面向对象的编程语言必须支持这三种特性。
封装
封装 encapsulation
特性指的是把一组相关联的数据和函数的实现过程隐藏起来,外面的代码通过消息传递机制发送消息给能看见的函数,数据完全不可见。
消息传递机制 Message Passing
是指一个对象通过接收消息、处理消息、传出消息或使用其它类的方法来实现一定功能。
在面向对象编程语言中,类 class
中的公共函数和私有成员变量为封装数据和函数提供了有利的支持,如下所示。
// date.h
class Date {
public:
Date(int year, int month, int day);
void print();
protected:
int year;
int month;
int day;
};
// date.cpp
#include <iostream>
#include "Date.h"
using namespace std;
Date::Date(int y, int m, int d) {
year = y;
month = m;
day = d;
}
void Date::print() {
cout<<year<<"-"<<month<<"-"<<day<<endl;
}
// main.cpp
int main() {
Date date = Date(1974, 2, 7);
date.print();
return 0;
}
// 打印: 1974-2-7
在 date.h
头文件中定义的 Date
类有 year
、month
和 day
三个成员变量声明了 private
级别的访问控制,编译器会禁止对这三个变量进行直接访问。想访问这三个变量,只有在 date.cpp
源文件中才可以访问并改变。print
方法声明了 public
级别的访问控制,可以在其它地方进行调用,是 Date
类对外暴露的方法。
继承
继承 inheritance
是指对已存在的类上构造新类。继承已存在的类就是复用这些类的属性和行为。在此基础上,还可以添加新的属性和行为,以满足新的需求。被继承的类被称为父类,继承父类的类被称为子类。当以一个类从多个父类继承时,称之为多重继承。
简而言之,继承的主要作用是让我们可以在某个作用域内对外部定义的某一组变量与函数进行覆写。
// DateTime.h
class DateTime : public Date {
public:
DateTime(int y, int mon, int d, int h, int min, int sec);
private:
int hour;
int minute;
int second;
};
// DateTime.cpp
DateTime::DateTime(int y, int mon, int d, int h, int min, int sec) : Date(y, mon, d) {
year = y;
month = mon;
day = d;
hour = h;
minute = min;
second = sec;
}
// main.cpp
int main() {
DateTime dateTime = DateTime(1974, 2, 8, 5, 10, 43);
dateTime.print();
return 0;
}
// 打印: 1974-2-8
在 main
函数中,使用构造器创建了 dateTime
对象后,调用 print()
函数可以打印出 1974-2-8
,这是因为 DateTime
类继承了 Date
,可以调用 Date
里 print()
函数,而不用在 DateTime
里重新实现,实现代码的复用。
多态
多态 polymorphism
是指由继承而产生的相关的不同的类,其对象对同一消息会做出不同的响应。
如下所示,子类可以对父类的 print()
函数进行覆写,需要将父类 Date
中的 print()
函数改为虚函数。
// date.h
class Date {
public:
...
virtual void print();
...
};
然后在子类 DateTime
中,将父类实现的 print()
函数覆盖重写,并在子类的 print()
函数添加的 override
表示当前函数重写了父类的虚函数。
// DateTime.h
class DateTime : public Date {
public:
...
void print() override;
...
};
// DateTime.cpp
#include <iostream>
#include "DateTime.h"
using namespace std;
...
void DateTime::print() {
cout<<year<<"-"<<month<<"-"<<day<<" "<<hour<<":"<<minute<<":"<<second<<endl;
}
// main.cpp
int main() {
DateTime dateTime = DateTime(1974, 2, 8, 5, 10, 43);
dateTime.print();
return 0;
}
// 打印: 1974-2-8 5:10:43
当重新调用 main
函数时,打印的就是子类 DateTime
中实现的 print
函数。
main
函数中,可以对 DateTime
实例更改为父类的指针,在程序运行时,会根据具体指向的子类对象,来执行不同的函数,这种表示为多态。
int main() {
Date * date = new DateTime(1974, 2, 8, 5, 10, 43);
date->print();
return 0;
}
// 打印: 1974-2-8 5:10:43
如上所述,类中的每个虚函数的地址都被记录在一个名叫 vtable
的数据结构里。对虚函数的每次调用都要先查询这个表,其衍生类的构造函数负责将该衍生类的虚函数地址加载到整个对象的 vtable
中。这里的多态其实就是函数指针的一种应用。
继承是多态的基础,多态是继承的目的。合理运用多态,能增强程序的简洁性、灵活性、可维护性、可重用性和可扩展性。
总结
人们普遍认为面向对象编程就是封装 encapsulation
、继承 inheritance
、多态 polymorphism
三个特性的有机结合,但是结构化编程也可以实现这三种特性,如 C
语言实现封装特性。
// Date.h
struct Date;
struct Date* createDate(int year, int month, int day);
void print(struct Date * date);
// Date.c
struct Date {
int year, month, day;
};
struct Date* createDate(int year, int month, int day) {
struct Date * date = malloc(sizeof(struct Date));
date->year = year;
date->month = month;
date->day = day;
return date;
}
void print(struct Date * date) {
printf("%d-%d-%d", date->year, date->month, date->day);
}
// main.c
int main() {
struct Date * date = createDate(1974, 12, 12);
print(date);
return 0;
}
// 打印: 1974-12-12
C语言实现的封装特性如上所示。继承和多态也可以使用 C 语言实现。那除了这三个特性之外,面向对象还剩下了什么?
Alan Kay
首次提出了面向对象
概念,认为对象应该像生物细胞或网络上的单个计算机,只能通过消息
进行通信。对象内部隐藏的状态不可以被直接修改,只能通过消息
通信间接修改。
面向对象编程存在着很多不同的说法和意见,但面向对象思想面对软件种类和规模复杂度的提升提供了模块化思想,通过多态的手段对源代码中的依赖关系进行控制,构建出某种插件式架构,让高层策略性组件与底层实现组件相分离,底层组件可以被编译成插件,实现独立于高层组件的开发和部署。

原文始发于微信公众号(海人为记):面向对象编程 | 编程范式
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/27505.html