时间:2025-12-19 19:28
人气:
作者:admin
对于面向对象软件系统的设计而言,在支持可维护性的同时,提高系统的可复用性是一个至关重要的问题,如何同时提高一个软件系统的可维护性和可复用性是面向对象设计需要解决的核心问题之一。在面向对象设计中,可维护性的复用是以设计原则为基础的。每一个原则都蕴含一些面向对象设计的思想,可以从不同的角度提升一个软件结构的设计水平。
面向对象设计原则为支持可维护性复用而诞生,这些原则蕴含在很多设计模式中,它们是从许多设计方案中总结出的指导性原则。面向对象设计原则也是我们用于评价一个设计模式的使用效果的重要指标之一。
原则的目的: 高内聚,低耦合
| 名称 | 定义 |
|---|---|
| 单一职责原则 (Single Responsibility Principle, SRP) ★★★★☆ |
类的职责单一,对外只提供一种功能,而引起类变化的原因都应该只有一个。 |
| 开闭原则 (Open-Closed Principle, OCP) ★★★★★ |
类的改动是通过增加代码进行的,而不是修改源代码。 |
| 里氏代换原则 (Liskov Substitution Principle, LSP) ★★★★★ |
任何抽象类出现的地方都可以用他的实现类进行替换,实际就是虚拟机制,语言级别实现面向对象功能。 |
| 依赖倒转原则 (Dependence Inversion Principle, DIP) ★★★★★ |
依赖于抽象(接口),不要依赖具体的实现(类),也就是针对接口编程。 |
| 接口隔离原则 (Interface Segregation Principle, ISP) ★★☆☆☆ |
不应该强迫用户的程序依赖他们不需要的接口方法。一个接口应该只提供一种对外功能,不应该把所有操作都封装到一个接口中去。 |
| 合成复用原则 (Composite Reuse Principle, CRP) ★★★★☆ |
如果使用继承,会导致父类的任何变换都可能影响到子类的行为。如果使用对象组合,就降低了这种依赖关系。对于继承和组合,优先使用组合。 |
| 迪米特法则 (Law of Demeter, LoD) ★★★☆☆ |
一个对象应当对其他对象尽可能少的了解,从而降低各个对象之间的耦合,提高系统的可维护性。例如在一个程序中,各个模块之间相互调用时,通常会提供一个统一的接口来实现。这样其他模块不需要了解另外一个模块的内部实现细节,这样当一个模块内部的实现发生改变时,不会影响其他模块的使用。(黑盒原理) |
本节课围绕设计模式中开闭原则(Open/Closed Principle,OCP) 展开,以计算器案例为核心,对比传统实现与符合开闭原则的实现,讲解开闭原则的核心思想、应用场景及落地方式。
开闭原则是设计模式中最核心、最重要的原则(“其他原则可以不知道,但这个必须掌握”),核心是:对扩展开放,对修改关闭。
增加软件功能的正确方式是新增代码,而非修改已有源代码;修改已有代码易引发未知 bug,增加出错风险,且会提高调试、维护成本。
将加减乘除所有运算逻辑集中写在同一个计算器类中:
getResult方法中实现所有运算逻辑。通过「抽象类 + 多态」解耦,将不同运算拆分为独立类(单一职责),每个类仅负责一种运算,新增运算时仅需新增类,无需修改原有代码。
作为所有具体计算器的统一接口,约束核心行为:
class AbstractCalculator{
public:
// 纯虚函数:强制子类实现“获取运算结果”逻辑
virtual int getResult() = 0;
// 纯虚函数:强制子类实现“设置操作数”逻辑
virtual void setOperatorNumber(int a, int b) = 0;
};
每个运算(加法 / 减法 / 乘法 / 取模)对应一个子类,继承抽象基类并实现纯虚函数:
| 子类名称 | 核心实现逻辑 |
|---|---|
| AdditionCalculator(加法) | 重写setOperatorNumber初始化操作数,getResult返回mA + mB |
| SubtractionCalculator(减法) | 仅修改getResult为mA - mB,其余逻辑复用 |
| MultiplicationCalculator(乘法) | 仅修改getResult为mA * mB,其余逻辑复用 |
| DivisionCalculator (除法) | 仅修改getResult为mA / mB,其余逻辑复用 |
| ModuloCaculator(取模) | 仅修改getResult为mA % mB,其余逻辑复用 |
子类:
//加法计算器
class AdditionCalculator : public AbstractCalculator
{
public:
// 重写:设置操作数
virtual void setOperatorNumber(int a, int b)
{
this->mA = a;
this->mB = b;
}
// 重写:实现加法逻辑
virtual int getResult()
{
return mA + mB;
}
public:
int mA;
int mB;
};
//减法计算器
class SubtractionCalculator : public AbstractCalculator
{
public:
// 重写:设置操作数
virtual void setOperatorNumber(int a, int b)
{
this->mA = a;
this->mB = b;
}
// 重写:实现减法逻辑
virtual int getResult()
{
return mA - mB;
}
public:
int mA;
int mB;
};
//乘法计算器
class MultiplicationCalculator : AbstractCalculator
{
public:
// 重写:设置操作数
virtual void setOperatorNumber(int a, int b)
{
this->mA = a;
this->mB = b;
}
// 重写:实现乘法逻辑
virtual int getResult()
{
return mA * mB;
}
public:
int mA;
int mB;
};
//除法计算器
class DivisionCalculator : AbstractCalculator
{
public:
// 重写:设置操作数
virtual void setOperatorNumber(int a, int b)
{
this->mA = a;
this->mB = b;
}
// 重写:实现除法逻辑
virtual int getResult()
{
return mA * mB;
}
public:
int mA;
int mB;
};
//取模计算器 通过增加代码来实现
class ModuloCaculator :public AbstractCalculator
{
public:
// 重写:设置操作数
virtual void setOperatorNumber(int a, int b)
{
this->mA = a;
this->mB = b;
}
// 重写:实现取模逻辑
virtual int getResult()
{
return mA % mB;
}
public:
int mA;
int mB;
};
使用抽象基类指针指向具体子类对象,统一调用接口,新增运算仅需新增子类,无需修改调用逻辑:
void test01()
{
// 加法运算:抽象指针指向加法子类
AbstractCalculator* calculatorPtr = new AdditionCalculator;
calculatorPtr->setOperatorNumber(10, 20);
cout << "ret:" << calculatorPtr->getResult() << endl;
// 减法运算:仅替换子类对象,调用逻辑不变
calculatorPtr = new SubtractionCalculator;
calculatorPtr->setOperatorNumber(10, 20);
cout << "ret:" << calculatorPtr->getResult() << endl;
delete calculatorPtr;
}
=0)强制子类实现,保证接口统一;delete释放,避免内存泄漏;getResult),操作数设置逻辑可复用,符合 “修改关闭” 原则。AdditionCalculator); 开闭原则的本质是通过抽象化设计,将 “可变部分”(不同运算)封装为独立扩展单元,“不变部分”(抽象接口)固定不修改。核心实践是:抽象基类定义统一接口,具体实现通过子类扩展,利用多态实现灵活调用,最终达到 “对扩展开放、对修改关闭” 的设计目标。
别名:最少知识原则(Least Knowledge Principle)
核心思想:
一个类 / 对象应当尽可能少地了解其他类 / 对象的细节(“知道越少,耦合越弱”);
当两个类需要交互时,应通过必要的接口实现,避免直接暴露内部细节;
核心目标:降低类之间的耦合度,提高系统的可维护性和扩展性。
买房人需直接与每个楼盘(如楼盘 A、楼盘 B)打交道:逐个判断楼盘品质(高品质 / 低品质),符合需求才购买。
核心问题:
买房人需 “知道所有楼盘的细节”(如楼盘类的存在、品质属性);
若新增楼盘(如楼盘 C、D),需修改买房人的代码(新增判断逻辑),耦合度极高;
对应代码:test01() 函数(直接创建 BuildingA/BuildingB 对象,硬编码判断品质)。
新增 “中介类”,由中介维护所有楼盘信息,买房人仅需向中介传递 “需求(如高品质)”,无需关注具体楼盘;
中介类的核心作用:
封装 “楼盘管理” 细节(初始化、存储、查找);
对外暴露统一接口(如 “找符合品质的楼盘”),隔绝买房人与具体楼盘的直接交互;
优势:
买房人仅需 “知道中介”,无需知道具体楼盘;
新增楼盘时,仅需修改中介类,无需修改买房人代码(符合开闭原则)。
| 版本 | 定位 | 核心逻辑(对应 “楼盘购买” 场景) | 设计思想 |
|---|---|---|---|
| test01 | 初始版本(未应用迪米特法则) | 客户端直接创建BuildingA/BuildingB对象,硬编码判断楼盘品质,符合需求则调用sale() | 直接交互,客户端 “包办一切” |
| test02 | 迭代版本(应用迪米特法则) | 客户端仅通过Mediator(中介类)传递需求(如 “高品质”),由中介负责查找楼盘并返回结果,客户端仅调用sale() | 间接交互,中间层 “协调统筹” |
// 02 面向对象设计原则-迪米特法则.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <string>
#include <vector>
using namespace std;
//迪米特法则 又叫最少知识原则
class AbstractBuilding
{
public:
virtual void sale() = 0;
virtual string getQuality() = 0;
};
//楼盘A
class BuildingA : public AbstractBuilding
{
public:
BuildingA()
{
mQulity = "高品质";
}
virtual void sale()
{
cout << "楼盘A" << mQulity << "被售卖" << endl;
}
virtual string getQuality()
{
return mQulity;
}
public:
string mQulity;
};
//楼盘B
class BuildingB : public AbstractBuilding
{
public:
BuildingB()
{
mQulity = "低品质";
}
virtual void sale()
{
cout << "楼盘B" << mQulity << "被售卖" << endl;
}
virtual string getQuality()
{
return mQulity;
}
public:
string mQulity;
};
//中介类
class Mediator
{
public:
Mediator()
{
AbstractBuilding* aBuidingPtr = new BuildingA;
vBuilding.push_back(aBuidingPtr);
aBuidingPtr = new BuildingB;
vBuilding.push_back(aBuidingPtr);
}
~Mediator()
{
for (vector<AbstractBuilding*>::iterator it = vBuilding.begin(); it != vBuilding.end(); it++)
{
if ( *it != NULL)
{
delete* it;
}
}
}
//对外提供接口
AbstractBuilding* findMyBuilding(string quality)
{
for (vector<AbstractBuilding*>::iterator it = vBuilding.begin(); it != vBuilding.end(); it++)
{
if ((*it)->getQuality() == quality)
{
return *it;
}
}
return NULL;
}
public:
vector<AbstractBuilding*> vBuilding;
};
//客户端
void test01()
{
BuildingA* mAbstractBuildingAPtr = new BuildingA;
if (mAbstractBuildingAPtr->mQulity == "低品质")
{
mAbstractBuildingAPtr->sale();
}
BuildingB* mAbstractBuildingBPtr = new BuildingB;
if (mAbstractBuildingBPtr->mQulity == "低品质")
{
mAbstractBuildingBPtr->sale();
}
}
void test02()
{
Mediator* mediatorPtr = new Mediator;
AbstractBuilding* buildingPtr = mediatorPtr->findMyBuilding("高品质");
if (buildingPtr != NULL)
{
buildingPtr->sale();
}
else
{
cout << "没有符合您条件的楼盘楼盘!" << endl;
}
}
a.test01:完全违反迪米特法则
“知识范围过载”:客户端需掌握所有具体楼盘类的细节
必须知道BuildingA、BuildingB的存在(需直接创建其对象);
必须知道具体楼盘的内部属性mQulity(直接访问mQulity判断品质);
若新增楼盘(如BuildingC),客户端需额外学习BuildingC的类名、属性,违背 “最少知识”。
无接口隔离:客户端直接操作具体类的内部属性(如mQulity),而非通过统一接口,暴露了楼盘类的实现细节。
b.test02:完全符合迪米特法则
“知识范围最小化”:客户端仅需了解 2 个 “直接朋友”(符合 “只与直接朋友交谈” 口诀)
Mediator(中介类):客户端唯一交互对象,仅需调用其findMyBuilding()接口传递需求;
AbstractBuilding(抽象基类):仅需知道其sale()接口(中介返回抽象指针,客户端无需知道具体是BuildingA还是BuildingB);
客户端无需了解任何具体楼盘类(BuildingA/BuildingB)的细节,完全隔绝 “陌生人”(具体楼盘类)。
接口隔离实现:通过中介的findMyBuilding()和抽象基类的sale()统一接口,隐藏了楼盘的创建、存储、查找逻辑,符合 “不暴露内部细节” 的要求。
(1)test01:强耦合(客户端与具体类深度绑定)
依赖对象:客户端直接依赖BuildingA、BuildingB两个具体类(代码中显式new BuildingA()/new BuildingB());
耦合表现:
若BuildingA的属性名修改(如mQulity改为quality),客户端代码需同步修改(mAbstractBuildingAPtr->mQulity需改为mAbstractBuildingAPtr->quality);
若删除BuildingB,客户端需删除new BuildingB()及对应的判断逻辑,“牵一发而动全身”。
(2)test02:弱耦合(客户端与抽象 / 中间层绑定)
依赖对象:客户端仅依赖Mediator(中间层)和AbstractBuilding(抽象基类),不依赖任何具体楼盘类;
耦合表现:
具体楼盘类的修改(如BuildingA的mQulity改名),仅需同步修改BuildingA的getQuality()实现(返回新属性名),客户端无感知;
新增 / 删除具体楼盘类,客户端代码完全无需修改,仅需调整中介类的内部逻辑(如Mediator构造函数中新增BuildingC)。
以 “新增楼盘BuildingC(品质为 “中品质”)” 为例,对比两个版本的扩展成本:
a.test01:扩展成本极高(客户端需全量修改)
需新增 3 处代码:
// 1. 新增BuildingC对象创建
BuildingC* mAbstractBuildingCPtr = new BuildingC;
// 2. 新增品质判断逻辑
if (mAbstractBuildingCPtr->mQulity == "中品质")
{
mAbstractBuildingCPtr->sale();
}
// 3. (若遗漏)可能还需手动释放`mAbstractBuildingCPtr`,否则内存泄漏
b.test02:扩展成本极低(仅修改中间层)
仅需修改 1 处代码(Mediator构造函数):
Mediator()
{
// 原有代码保留
AbstractBuilding* aBuidingPtr = new BuildingA;
vBuilding.push_back(aBuidingPtr);
aBuidingPtr = new BuildingB;
vBuilding.push_back(aBuidingPtr);
// 新增:加入BuildingC
aBuidingPtr = new BuildingC;
vBuilding.push_back(aBuidingPtr);
}
a.test01:职责混乱(客户端承担过多非自身职责)
b.test02:职责清晰(各角色各司其职)
客户端:仅负责 “提出需求”(传递品质参数)和 “触发售卖”(调用sale()),职责单一;
Mediator(中介):负责 “楼盘管理”(创建、存储、查找)和 “内存释放”,承担 “协调交互” 的核心职责;
具体楼盘类(BuildingA/BuildingB):仅负责 “自身售卖逻辑”(重写sale())和 “提供品质查询”(重写getQuality()),不关心外部交互;
优势:某一角色的逻辑修改(如中介查找规则调整),不影响其他角色,可维护性大幅提升。
a.test01:内存管理混乱,易泄漏
问题:客户端需手动创建BuildingA/BuildingB对象,但代码中未显式释放(如delete mAbstractBuildingAPtr),会导致内存泄漏;
隐患:若客户端忘记释放,或释放逻辑与创建逻辑分离(如创建在函数开头,释放在结尾),易因代码修改导致 “漏释放”。
b.test02:内存管理统一,安全可控
~Mediator()
{
for (vector<AbstractBuilding*>::iterator it = vBuilding.begin(); it != vBuilding.end(); it++)
{
if (*it != NULL) delete *it; // 统一释放,无泄漏风险
}
}
| 迭代目标 | test01的痛点 | test02的解决方案 | 最终价值 |
|---|---|---|---|
| 降低耦合度 | 客户端与具体类强绑定,修改牵一发而动全身 | 引入中介层,客户端仅依赖抽象 / 中间层 | 系统更灵活,修改影响范围最小化 |
| 提升扩展性 | 新增楼盘需修改客户端代码 | 新增楼盘仅修改中介构造函数 | 支持快速扩展,符合 “开闭原则” |
| 简化客户端逻辑 | 客户端承担创建、判断、释放等多职责 | 客户端仅提需求,中介包办 “找楼盘” | 客户端代码精简,易维护 |
| 保障内存安全 | 易漏释放,内存泄漏风险高 | 中介析构函数统一释放,无泄漏 | 提升程序稳定性,避免内存问题 |
| 契合迪米特法则 | 客户端 “知道过多”,违反最少知识原则 | 客户端仅与 “直接朋友” 交互,隔绝陌生人 | 符合面向对象设计规范,代码可复用性更高 |
所有案例均基于 “车” 的场景,先定义核心类结构:
// 抽象车类(抽象基类,定义共性行为)
class AbstractCar
{
public:
// 纯虚函数:所有车的共性行为——启动
virtual void run() = 0;
};
// 具体车类1:大众车(继承抽象车,实现启动逻辑)
class Dazhong : public AbstractCar
{
public:
virtual void run()
{
cout << "大众车启动..." << endl;
}
};
// 具体车类2:拖拉机(继承抽象车,实现启动逻辑)
class Tuolaji :public AbstractCar
{
public:
virtual void run()
{
cout << "拖拉机启动..." << endl;
}
};
// 问题:通过“继承具体类”实现“人开车”,违反合成复用原则
class Person : public Tuolaji // 人继承拖拉机
{
public:
void Doufeng() { run(); } // 兜风=调用拖拉机的启动方法
};
class PersonB : public Dazhong // 另一个人继承大众车
{
public:
void Doufeng() { run(); } // 兜风=调用大众车的启动方法
};
// 优化:通过“组合”(抽象车指针作为成员)实现“人开车”
class Person
{
public:
// 1. 设置车型(传入抽象车指针,支持所有具体车型)
void setCar(AbstractCar* aCarPtr)
{
this->carPtr = aCarPtr;
}
// 2. 兜风行为(调用当前车型的启动方法,自动适配具体车型)
void Doufeng()
{
this->carPtr->run(); // 多态调用:传什么车就执行什么车的run
// 内存管理:避免内存泄漏(使用后释放车对象)
if (this->carPtr != NULL)
{
delete this->carPtr;
this->carPtr = NULL;
}
}
private:
// 组合核心:依赖抽象车类指针(而非具体类)
AbstractCar* carPtr;
};
// 测试函数:验证组合的灵活性
void test02()
{
Person* p = new Person;
// 情况1:开大众车兜风
p->setCar(new Dazhong);
p->Doufeng(); // 输出“大众车启动...”
// 情况2:切换开拖拉机兜风(无需修改Person类)
p->setCar(new Tuolaji);
p->Doufeng(); // 输出“拖拉机启动...”
// 释放Person对象
delete p;
}
int main()
{
test02();
return 0;
}
// 02 面向对象设计原则-合成复用原则.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
using namespace std;
// 抽象车类(抽象基类,定义共性行为)
class AbstractCar
{
public:
// 纯虚函数:所有车的共性行为——启动
virtual void run() = 0;
};
// 具体车类1:大众车(继承抽象车,实现启动逻辑)
class Dazhong : public AbstractCar
{
public:
virtual void run()
{
cout << "大众车启动..." << endl;
}
};
// 具体车类2:拖拉机(继承抽象车,实现启动逻辑)
class Tuolaji :public AbstractCar
{
public:
virtual void run()
{
cout << "拖拉机启动..." << endl;
}
};
#if 0
//针对具体类 不使用继承
class Person : public Tuolaji {
public:
void Doufeng() { run(); }
};
class PersonB : public Dazhong {
public:
void Doufeng() { run(); }
};
#endif
//可以使用组合
class Person
{
public:
void setCar(AbstractCar* aCarPtr)
{
this->carPtr = aCarPtr;
}
void Doufeng()
{
this->carPtr->run();
if (this->carPtr != NULL)
{
delete this->carPtr;
this->carPtr = NULL;
}
}
public:
AbstractCar* carPtr;
};
void test02()
{
Person* p = new Person;
p->setCar(new Dazhong);
p->Doufeng();
p->setCar(new Tuolaji);
p->Doufeng();
delete p;
}
//继承和组合 优先使用组合
int main()
{
test02();
return 0;
}
| 对比维度 | 继承(版本 1) | 组合(版本 2) |
|---|---|---|
| 依赖对象 | 具体类(Tuolaji/Dazhong) | 抽象类(AbstractCar) |
| 耦合度 | 高(强绑定) | 低(松耦合) |
| 扩展性 | 差(新增车型需新建人类) | 好(新增车型仅需加具体车类) |
| 灵活性 | 无法切换车型 | 可通过 setCar 灵活切换 |
| 符合原则 | 违反合成复用原则 | 符合合成复用原则 |


传统的设计模式通常是自顶向下逐级依赖,这样,底层模块,中间层模块和高层模块的耦合度极高,若任意修改其中的一个,很容易导致全面积的修改,非常麻烦,那么依赖倒转原则利用多态的先天特性,对中间抽象层进行依赖,这样,底层和高层之间进行了解耦合。
本节课围绕面向对象设计原则中的依赖倒转原则展开讲解,结合单一职责原则,通过银行业务办理的代码案例,对比传统开发方式与遵循依赖倒转原则开发方式的差异,阐述该原则的核心思想与实践价值。
传统开发采用自上而下的层级依赖模式:高层业务逻辑模块依赖中层功能模块,中层功能模块依赖底层具体实现模块,呈现 高层→中层→底层 的单向依赖链,类似函数调用的层层嵌套。
BankWorker类负责存款、支付、转账 3 个业务),违背单一职责原则。// 04 面向对象设计原则-依赖倒转原则原则.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
using namespace std;
//银行工作人员
class BankWorker
{
public:
void saveService()
{
cout << "办理存款业务..." << endl;
}
void payService()
{
cout << "办理支付业务.." << endl;
}
void tranferService()
{
cout << "办理转账业务.." << endl;
}
};
//中层模块
void doSaveBusiness(BankWorker* worker)
{
worker->saveService();
}
void doPayBusiness(BankWorker* worker)
{
worker->payService();
}
void doTransferBusiness(BankWorker* worker)
{
worker->tranferService();
}
void test01()
{
BankWorker* worker = new BankWorker;
doSaveBusiness(worker);//办理存款业务
doPayBusiness(worker);//办理支付业务
doTransferBusiness(worker);//办理转账业务
}
int main()
{
std::cout << "Hello World!\n";
}
BankWorker类包揽 3 项业务,职责过重。doSaveBusiness等直接依赖BankWorker具体类,若新增 “理财业务”,需要修改BankWorker类并新增对应的中层函数,扩展性差。定义抽象类AbstractWorker,声明统一的业务接口doBusiness,作为所有具体业务类的父类。
class AbstractWorker
{
public:
virtual void doBusiness() = 0; // 纯虚函数,定义抽象接口
};
将原BankWorker类的 3 项业务拆分为 3 个独立的具体类,每个类只负责一项业务,并继承抽象类实现接口。
// 存款业务类
class SaveBanker : public AbstractWorker
{
public:
virtual void doBusiness() { cout << "办理存款业务..." << endl; }
};
// 支付业务类
class PayBanker : public AbstractWorker
{
public:
virtual void doBusiness() { cout << "办理支付业务..." << endl; }
};
// 转账业务类
class TransferBanker : public AbstractWorker
{
public:
virtual void doBusiness() { cout << "办理转账业务..." << endl; }
};
重构中层业务函数doNewBusiness,参数类型改为抽象类指针,利用多态性,传入不同的子类对象即可执行对应的业务。
void doNewBusiness(AbstractWorker * worker)
{
worker->doBusiness(); // 多态调用,具体执行逻辑由子类决定
delete worker;
}
调用时直接传入具体业务类的对象,无需修改原有函数即可灵活切换业务。
void test02()
{
doNewBusiness(new TransferBanker);
doNewBusiness(new SaveBanker);
doNewBusiness(new PayBanker);
}
AbstractWorker,具体业务类的修改不会影响中层函数。AbstractWorker的FinancingBanker类,无需修改任何原有代码,符合开闭原则。在需要传递类对象作为函数参数时,优先使用抽象类 / 接口类型作为参数类型,而非具体类类型,利用多态特性提升代码的灵活性。
参考资料来源:黑马程序员