1. 开闭原则
最基本的原则
一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
当要扩展功能的时候,应该添加代码,而不应该修改现有代码。
这里修改代码的意思是在原有代码的基础上增加或删除。
而添加代码的意思是添加新的子类。
修改原有代码,可能导致现在是使用原有代码的那部分代码出错。也即,对扩展开放,对修改关闭。
用抽象构建框架,用实线拓展细节。对不变部分进行抽象,保证框架稳定。对易变部分,采用从抽象中派生的方法进行拓展。
//不好的模式,违反了开闭原则
```c++
//忽略了#ifndef
// ./Clerk.h
#include <iostream>
class Clerk
{
public:
void job1();
void job2();
void job3();
void job4();
}
// ./main.cc
#include "Clerk.h"
int main()
{
Clerk clerk;
clerk.job1();
//...
return 0;
};
符合开闭原则的
./ ./AbBankClerk.h
class AbBankClerk
{
public:
virtual void doit()=0;
};
./ ./SaveClerk.h
class SaveClerk:public:AbBankClerk
{
public:
virtual void doit()
{
//dosomething
}
};
// ./main.cc
#include "SaveClerk.h"
#include "AbBankClerk.h"
int main()
{
AbBankClerk *saveClerk=new SaveClerk();
saveClerk->doit();
return 0;
}
UML
2. 依赖倒置
高层的抽象不依赖度底层的抽象,而应该依赖该底层抽象的抽象。
高层抽象,依赖于抽象层。抽象不应该依赖于实现。
实现了解耦。
相对于复用(能够使用前人写的代码),而多态是能够使用后来人写的代码。
//不好的代码
// ./Book.h
class Book
{
public:
string context();
};
// ./Mother.h
class Mother
{
public:
string tellStory(Book& book);
};
上面的代码,Mother
依赖于book
类,如果Mother
要想要读Paper
那么就需要修改代码。
所以改为
//依赖倒置
// ./AbPaper.h
class AbPaper
{
public:
virtual string context()=0;
}
// ./Book.h
class Book:public:AbPaper
{
public:
virtual string context()
{
//doit
}
}
// ./Mother.h
class Mother
{
public:
string tellStory(AbPaper *reader);
}
这样修改,即使以后有新的读物添加,那么直接继承AbPaper
就可以被Mother
使用。
UML
3. 接口隔离原则
不应该让客户端使用他所不依赖的接口,一个接口应该只对外提供一种功能。
一个类应该依赖于最小的借口
也就是说,借口提供的功能应该是单一的。
例如上面的AbPaper
//不好的模式
// ./AbPaper.h
class AbPaper
{
public:
virtual string context()=0;
virtual double fee()=0
};
上面的抽象类,还提供一种查询价格的接口,那么依赖于这个抽象层的类,比如Mother
将额外的可以使用fee()
,而这个功能可能是Mother
可能不需要的。
所以接口隔离原则将的就是,将抽象层做的尽量小,提供必需的借口,不要复用。
4. 里氏替换原则
比如说一个函数,接受一个参数T1类型,如果有另一个类型T2,在不考虑类型的情况下,可以完全用T2的实例代替T1的实例传入参数,不影响函数的功能,那么T2是T1的子类型。(反之亦然)
这里的关键其实不在于T1和T2应该是继承关系,而在于,不影响函数的功能。
继承存在,必然存在虚函数,而子类可能会无意间覆盖父类的函数使得多态失效。
这个原则通俗来讲就是:
- 子类可以扩展父类的功能,但是不能修改父类原有的功能(不要重载、覆盖父类函数)。
- 优先使用组合的而不是继承。
//没太看懂
//里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。
//它包含以下4层含义:
//子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
//子类中可以增加自己特有的方法。
//当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
//当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
5. 迪米特法则(最少知道原则)
类之间应该最少的了解。解耦。低耦合,高内聚
举例:例如A依赖于B
- B应该将类的逻辑封装在自己类的内部,除了对外提供
public
方法以外,不要泄露额外的信息 - 对于依赖具有某种特征的对象,那么就在中间做一层抽选性,参考依赖倒置
- 对于那些为了方便只会用到几次的操作,应该使用“朋友类”来访问
当然也不是说完全对别的类不知道,比如,类的成员变量,方法参数,返回值,是可以知道的。
对于那些不应该逻辑上不应该直接访问的对象,那么应该通过“朋友类”来访问。
6. 单一职责原则
只存在一个原因会使类变更,也就是类只负责一种功能
难说,如果发现类的某个方法,存在缺陷,比如类的某种实例并不能使用该方法,那么该类可能不完善,需要重新设计。