一、面相对象编程(OOP)
客观现实世界 | 程序世界 |
---|---|
抽象 | 实例化 |
对象 —> 类 | 类(class) —> 对象(object) |
面向对象编程范式
面向对象范式步骤 | ||
---|---|---|
面向对象分析 | what | 项目需求(变化) |
面向对象设计 | how | 以期用最小的代价适应项目需求变化 |
面向对象编程(OOP) | do | 编码实现 |
面向对象的三大特征是:封装、继承、多态
大体逻辑如下:
对于现实生活中任意一个领域:
-
必然是一个分类体系 —> “继承” —> 派生类对象 —> 对象的创建和销毁
-
必然存在个体多样性 —> “多态” —> 抽象类、虚函数
-
对于外行人不必了解领域内的细节 —> “封装” —> 权限修饰符
既然涉及到对象,必然涉及到对象的存储和相关计算 —> 需要"数据结构 + 算法"
二、UML统一建模语言(类图、类之间的关系)
1. 继承(泛化 generalization)
~ 表达的是:A is B 的关系

2. 关联关系
~ 表达的是:A has B 的关系
【分为:单向的、双向的】
【关系是固定的】
【彼此并不负责对方的生命周期】
【一般使用指针或引用】

3. 聚合关系
表达的是:对象之间为整体和部分的关系
- 【比较强的关联关系】
- 【"整体"不负责"部分"对象的销毁】
- 【整体不存在了,部分仍可作为零件单独存在】

4. 组合关系
表达的是:对象之间为整体和局部的关系【A has B】
- 【更强的关联关系】
- 【"整体"负责"部分"对象的销毁】
- 【整体不存在了,部分也不复存在】

5. 依赖关系
表达的是:从语义上来说是 A use B 的关系(是偶然的、临时的,并非固定的)
- 【要借助函数调用来完成:】
- 【B作为A的成员函数参数】
- 【B作为A的成员函数的局部变量】
- 【A的成员函数调用B的静态方法】

~【A use B 是通过函数来表现,而 A has B 是通过数据成员来表现】
6. 各关系的比较
继承体现的是类之间的纵向关系,其余四种体现的是类之间的横向关系
耦合强弱:依赖 < 关联 < 聚合 < 组合
从语义上来看:
- 继承(A is B)
- 关联、聚合、组合(A has B)
- 依赖(A use B)
- 当组合与依赖结合时,可以替代继承
组合+依赖(基于对象) v.s. 继承(面向对象)
重点补充:—>【如何判断两个类之间是什么关系?】
可以使用排除法:
例如C++多线程编程中,条件变量和互斥锁之间的关系
- 继承(A is B):显然不是;
- 组合(整体and局部,并且整体要负责局部的生命周期):显然也不是,二者生命周期互不干扰;
- 聚合:显然也不是,不是整体和局部的关系
- 依赖还是关联?
①依赖关系(A use B):是临时的,偶然的(函数形参,局部变量,调用静态方法)
②关联关系(A has B):是固定的,A has B
使用条件变量,就必要要使用互斥锁,没有互斥锁不行,显然是固定的关系,而且是单向的 —> 从而确定是关联关系
三、面向对象设计法则(SOLID五原则)
总原则:高内聚、低耦合
SOLID的5原则:
- 单一职责原则(Single Responsibility Principle)
- 开闭原则(Open Closed Principle)
- 里氏替换原则(Liscov Substitution Principle)
- 接口分离原则(Interface SegregationPrinciple)
- 依赖倒置原则(Dependency Inversion Principle)
1、单一职责原则(Single Responsibility Principle)
即:一个类,只干一件事情(意思是,只完成一个功能,并不是说只有一个函数)
(当一个类包含的功能太多时,会带来不必要的麻烦:比如,本来只需要其中一个功能,但必须把整个类引入,导致其他许多不必要的功能也不得已被引入了))
【例如:】
【左侧类本身只是想完成计算面积的功能,这需要依赖于Rectangle类,而Rectangle类中又包含了draw()方法,即使本来完全不应该用到,也只好因此而引入GUI库】
【重构成如下设计:】
2、开闭原则(Open Closed Principle)
对扩展开放,对修改封闭
【核心思想是:面对抽象编程,而不面对具体编程】
【让类依赖于特定的抽象】
【例如:经典例子:Figure <— Rectangle、Triangle、Circle】
3、里氏替换原则(Liscov Substitution Principle)
核心思想是:派生类必须能够替换基类(即,基类中的某些功能,派生类不需要的话,就要对基类进行拆分)
【例如:鸵鸟不会飞,因此基类Bird中就不应该有fly()方法】
4、接口分离原则(Interface Segregation Principle)
【核心思想是:使用多个小的专门的接口,而不要使用一个大的总接口】
【例如:同样是鸵鸟】
【重构成如下设计:】
【原先设计:对于鸵鸟而言,鸵鸟不会飞,就无法使用该接口,可见"肥接口"导致接口通用性下降】
【重构设计:鸵鸟不会飞,就使用IBird接口;针对会飞的鸟,可以在该接口的基础上,再扩展新的接口,加入fly()方法,会飞的鸟可以使用此接口】
5、依赖倒置原则(Dependency Inversion Principle)
【核心思想是:面对接口编程,依赖于抽象】
【高层模块不依赖于低层模块,而是二者都同时依赖于抽象;抽象不依赖于具体,具体依赖于抽象】
【例如:】
6、迪米特法则(Law Of Demeter,最少知道原则)
一个软件实体应该尽可能少的与其他实体发生相互作用,同样是为了解耦
【例如:需要托关系找人完成一件事,我没必要也尽量不要直接去找那个人(实际上不熟找了也未必有用),而是通过我的朋友(我的朋友跟这个人很熟),让我的朋友替我去找他,作为中间人】
7、组合/聚合复用原则(Composite/Aggregate Reuse Principle,即 CARP)
尽量使用聚合、组合来达到复用,而少用继承(即,一个类中有另一个类的对象)
【因为使用继承常常会破坏基类-派生类的开闭原则】