面向对象设计原则
如何同时提高一个软件系统的可维护性和可复用性是面向对象设计需要解决的核心问题之一。在面向对象设计中,可维护性的复用是以设计原则为基础的。每一个原则都蕴含一些面向对象设计的思想,可以从不同的角度提升一个软件结果的设计水平。
面向对象设计原则为支持可维护性复用而诞生,这些原则蕴含在很多设计模式中,它们是从许多设计方案中总结出的指导性原则。面向对象设计原则也是我们用于评价一个设计模式的使用效果的重要指标之一。
原则的目的: 高内聚 低耦合
开闭原则案例:
这是一个计算器的案例:
#include <iostream>
using namespace std;
//开闭原则 对扩展开放 对修改关闭
//增加功能是通过增加代码实现的 而不是去修改源代码
//写一个抽象类
class AbstractCaculator {
public:
virtual int getResult() = 0;
virtual void setOperatorNumer(int a, int b) = 0;
};
//加法计算器
class PlusCaculator : public AbstractCaculator {
public:
virtual void setOperatorNumer(int a, int b) {
this->mA = a;
this->mB = b;
}
virtual int getResult() {
return mA + mB;
}
public:
int mA;
int mB;
};
//减法计算器
class MinuteCaculator : public AbstractCaculator {
public:
virtual void setOperatorNumer(int a, int b) {
this->mA = a;
this->mB = b;
}
virtual int getResult() {
return mA - mB;
}
public:
int mA;
int mB;
};
//乘法计算器
class MultiplyCaculator : public AbstractCaculator {
public:
virtual void setOperatorNumer(int a, int b) {
this->mA = a;
this->mB = b;
}
virtual int getResult() {
return mA * mB;
}
public:
int mA;
int mB;
};
void test01() {
//通过抽象类的指针对象去接收一个加法类计算器
AbstractCaculator* caculator = new PlusCaculator;
caculator->setOperatorNumer(10,20);
cout << "ret: " << caculator->getResult() << endl;
delete caculator;
//利用同一个对象去接收加法类计算器
caculator = new MinuteCaculator;
caculator->setOperatorNumer(10, 20);
cout << "ret: " << caculator->getResult() << endl;
delete caculator;
}
int main() {
test01();
//输出结果:ret: 30
// ret: -10
return 0;
}
总结:可以看出 使用抽象类作为一个接口而不是把所有功能都写在一个计算器类内部虽然看上去代码量变得多了 但是对于后期想要添加一个新的运算功能非常友好
每个功能都是单独的一个类 不需要去修改源代码 直接加一个新的类就可以实现功能的增加 符合开闭原则
迪米特法则案例:
这是一个客户选择楼盘的案例:
#include <iostream>
using namespace std;
#include <string>
#include <vector>
//迪米特法则 又叫最少知识原则
class AbstractBuilding {
public:
virtual void sale() = 0;
virtual string getQuality() = 0;
};
//楼盘A
class BuildingA : public AbstractBuilding {
public:
BuildingA() {
mQuality = "高品质";
}
virtual void sale() {
cout << "楼盘A" << mQuality << "被售卖!" << endl;
}
virtual string getQuality() {
return mQuality;
}
public:
string mQuality;
};
//楼盘B
class BuildingB : public AbstractBuilding {
public:
BuildingB() {
mQuality = "低品质";
}
virtual void sale() {
cout << "楼盘B" << mQuality << "被售卖!" << endl;
}
virtual string getQuality() {
return mQuality;
}
public:
string mQuality;
};
/*
//客户端调用
//这里没有用到抽象类 客户与BuildingA和B直接发生了交互
void test01() {
//创建一个BuildingA对象
BuildingA* ba = new BuildingA;
//直接用BuildingA对象去访问其内存的成员变量和成员函数
//在BuildingA中去寻找低品质楼盘
if (ba->mQuality == "低品质") {
ba->sale();
}
//同样的道理在BuildingB中去寻找低品质楼盘
BuildingB* bb = new BuildingB;
if (bb->mQuality == "低品质") {
bb->sale();
}
}
*/
//中介类
//这个类中提供了一个接口
//让用户不再需要创建BuildingA或BuildingB对象
class Mediator {
public:
Mediator() {
//这里用抽象类指针对象去接收BuildingA和BuildingB
//然后把两个对象压入vector容器里
AbstractBuilding* building = new BuildingA;
vBuilding.push_back(building);
building = new BuildingB;
vBuilding.push_back(building);
}
//对外提供接口
AbstractBuilding* findMyBuilding(string quality) {
//遍历容器
for (vector<AbstractBuilding*>::iterator it = vBuilding.begin(); it != vBuilding.end(); it++) {
//*it就是building对象 因为是一个指针 用->通过抽象类对象访问BuildingA和BuildingB的getQuality实现
//如果得到的品质返回值与传入参数相同 就返回这个品质
if ((*it)->getQuality() == quality) {
return *it;
}
}
//如果遍历没有找到匹配的返回值 就返回空
return NULL;
}
//析构函数
~Mediator() {
//遍历容器 不等于空就删除 手动释放堆区内容
for (vector<AbstractBuilding*>::iterator it = vBuilding.begin(); it != vBuilding.end(); it++) {
if (*it != NULL) {
delete* it;
}
}
}
public:
//创建一个vector成员变量 是抽象类指针类型的
//作为一个接口去访问它的子类存在vector中
vector<AbstractBuilding*>vBuilding;
};
//客户端
//这里使用了中介Mediator类
//不需要与BuildingA或者BuildingB直接交互
//而是通过抽象类接口
void test02() {
//创建一个中介类对象
Mediator* mediator = new Mediator;
//直接通过中介类对象去的成员函数去找高品质楼盘 用一个抽象类指针接收
AbstractBuilding* building = mediator->findMyBuilding("高品质");
//不等于空便是找到 于是用抽象类指针去访问BuildingA或者BuildingB内部的成员函数
if (building != NULL){
building->sale();
}
else {
//等于空即没有找到
cout << "没有符合您条件的楼盘!" << endl;
}
}
int main() {
test02();
//输出结果:楼盘A高品质被售卖!
return 0;
}
总结:当要开发一个子系统的时候 最少知识原则说不要把主系统的所有细节都暴露给子系统 只要暴露一个接口就可以了 就好比这里客户去选择楼盘 可以想象一下 客户不需要去一家一家的实地考察每一个楼盘 只要通过一个中介接口 了解到哪些是高品质楼盘即可达到目的
合成复用原则
#include <iostream>
using namespace std;
//面向对象设计原则-合成复用原则
//继承和组合优先使用组合
//抽象类
class AbstractCar {
public:
virtual void run() = 0;
};
//大众
class Volkswagen : public AbstractCar {
public:
virtual void run() {
cout << "大众汽车启动..." << endl;
}
};
//拖拉机
class Tuolaji : public AbstractCar {
public:
virtual void run() {
cout << "拖拉机启动..." << endl;
}
};
/*
//继承拖拉机 调用拖拉机的run 针对具体类
class Person1 : public Tuolaji{
public:
void Drive() {
run();
}
};
//继承大众 调用大众的run 针对具体类
class Person2 : public Volkswagen {
public:
void Drive() {
run();
}
};
从上面的两个Person类可以看出 用继承针对具体类不好
每次调用不同的run都需要一个新的person类*/
//所以可以使用组合
class Person {
public:
//初始化抽象类指针变量接口
void setCar(AbstractCar* car) {
this->car = car;
}
//使用接口去访问大众和拖拉机里的run 顺便释放堆区内存
void Drive() {
this->car->run();
if (this->car != NULL) {
delete this->car;
this->car = NULL;
}
}
public:
//在Person类内部创建一个抽象类变量而不是继承
AbstractCar* car;
};
//通常我们看到的多态是直接使用抽象类对象作为接口去接收其子类对象
//而这里我们把抽象类作为一个成员变量封装在了Perosn类里 然后去调用抽象类的子类对象
//所以这就是组合的特点
void test01() {
//创建一个Person类对象
Person* p = new Person;
//new一个大众对象作为参数传入 用抽象类指针变量接收
//这时候 抽象类指针变量 就变成了 抽象类指针对象
p->setCar(new Volkswagen);
//Drive内部封装了一个抽象类指针对象指向大众的成员函数run
p->Drive();
//使用同一个Perosn类去调用拖拉机的run
p->setCar(new Tuolaji);
p->Drive();
delete p;
}
int main() {
test01();
//输出结果:大众汽车启动...
// 拖拉机启动...
return 0;
}
总结:说白了 这个原则就一个结论:继承和组合优先使用组合 这句话中的继承指的是Person类不封装抽象类直接去继承各个汽车 而这句话中的组合就是Person类封装了一个抽象类 把抽象类作为一个成员变量 归根结底 还是在教我们多使用抽象类 即多态的精髓
依赖倒转原则
传统的设计模式自顶向下逐级依赖 这样的话每一层的耦合度极高 修改任意的其中一个都会导致全面及的修改
#include <iostream>
using namespace std;
//依赖倒转原则
//上层模块
class BankWorker {
public:
void saveService() {
cout << "办理存款业务..." << endl;
}
void payService() {
cout << "办理支付业务..." << endl;
}
void transferService() {
cout << "办理转账业务" << endl;
}
};
//中层模块
void doSaveBusiness(BankWorker* worker) {
worker->saveService();
}
void doPayBusiness(BankWorker* worker) {
worker->payService();
}
void doTransferBusiness(BankWorker* worker) {
worker->transferService();
}
//底层模块
void test01() {
BankWorker* worker = new BankWorker;
doSaveBusiness(worker);//办理存款业务
doPayBusiness(worker);//办理支付业务
doTransferBusiness(worker);//办理转账业务
}
int main() {
test01();
return 0;
}
这里我们发现使用传统设计模式下 底层依赖于中层 中层依赖于上层 耦合性太强
这里我们可以改变一下 高层业务逻辑是依赖于抽象层 而具体的实现也是依赖于抽象层
#include <iostream>
using namespace std;
//依赖倒转原则
//银行工作人员 抽象层
class AbstractWorker {
public:
virtual void doBusiness() = 0;
};
//专门负责办理存款业务的工作人员
class SaveBankWorker : public AbstractWorker {
public:
virtual void doBusiness() {
cout << "办理存款业务..." << endl;
}
};
//专门办理支付业务的工作人员
class PayBankWorker : public AbstractWorker {
public:
virtual void doBusiness() {
cout << "办理支付业务..." << endl;
}
};
//专门办理转账业务的工作人员
class TransferBankWorker : public AbstractWorker {
public:
virtual void doBusiness() {
cout << "办理转账业务..." << endl;
}
};
//中层业务模块 依赖于抽象层 比业务更高一个层次了
//一个抽象类指针对象可以访问所有业务
void doNewBusiness(AbstractWorker* worker) {
worker->doBusiness();
delete worker;
}
//具体的实现层也是依赖于抽象层
void test02() {
//()里new出来的对象都用同一个抽象类指针去接收
doNewBusiness(new TransferBankWorker);
doNewBusiness(new PayBankWorker);
doNewBusiness(new SaveBankWorker);
}
int main() {
test02();
//输出:办理转账业务...
// 办理支付业务...
// 办理存款业务...
return 0;
}
总结:这里的精髓其实就是中层是使用抽象类作为一个接口 还是使用非抽象类去访问 因为抽象类作为接口可以访问所有子类对象 所以中层业务和具体实现层都可以依赖于抽象类对象