1 单一职责原则
就一个类而言,应该仅有一个引起它变化的原因。
如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会造成脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏。
软件设计真正要做的内容,就是发现职责并把那些职责相互分离。其实要去判断是否应该分离出类来也不难,那就是如果你能够想到多于一个动机去改变一个类,那么这个类就有多于一个的职责,就应该考虑类的职责分离。
2 开放-封闭原则(开-闭原则)
开放-封闭原则,是说软件实体(类、模块、函数等等),应该可以扩展,但是不可修改。
两个特征,一是“对于扩展是开放的”,但“对于修改是关闭的”。
遵守开放-封闭原则的原因是,在软件开发过程中,需求变更是难免的,如何在需求发生变更时尽量少的修改原有代码,减少代码重构。需要软件设计人员在设计代码时,尽量猜测后面潜在的变化,然后构造抽象来隔离那些变化。如果一开始没有对变化进行良好的抽象,那么在变化发生时应该立即采取行动。比如在简单工厂设计模式中,一开始把加法功能写在了客户端进行实现,后面添加了减法功能,那么应该立即意识到需要对计算操作进行抽象成单独的类,拖得时间越久,需要修改的地方越多。如下图所示,在意识到需要进行抽象之后,将类的结构由下图第一行变更为第二行。
面对需求,对程序的改动是通过增加新代码进行的,而不是更改现有的代码。
开放-封闭原则是面向对象设计的核心所在。遵循这个原则可以带来面向对象设计所声称的巨大好处,也就是可维护、可扩展、可复用、灵活性好。开发人员应该仅对程序中呈现出频繁变化的那些部分做出抽象,然而,对于应用程序中的每个部分都刻意进行抽象同样不是一个好主意。拒绝不成熟的抽象和抽象本身一样重要。
3 依赖倒转原则(依赖倒置原则)
依赖倒转原则(dependency inversion principle,DIP).
3.1 里氏代换原则
一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且觉察不出父类对象和子类对象的区别。也就是说,在软件里面,把父类都替换为它的子类,程序的行为没有变化。简言之,子类型必须能够替换掉它们的父类型。
只有当子类可以替换掉父类,软件单位的功能不受到影响时,父类才能真正被复用,而子类也能够在父类的基础上增加新的功能。
正是由于里氏代换原则才使得开放-封闭成为了可能。
3.2 依赖倒转原则
高层模块不应该依赖低层模块,两个都应该依赖抽象。
抽象不应该依赖于细节,细节应该依赖于抽象。
依赖倒转其实可以说是面向对象设计的标志,用哪种语言来编写不重要,如果编写时考虑的都是如何针对抽象编程而不是针对细节编程,即程序中所有的依赖都是终止于抽象类或者接口,那就是面向对象的设计,反之就是过程化的设计了。
高层模块不要直接调用低层模块,而是中间添加抽象类或者接口。
3.3 依赖倒转原则实现
本节内容参考自:https://www.cnblogs.com/lsgxeva/p/7773009.html
//Computer.h
#include "HardDisk.h"
#include "Memory.h"
#include "CPU.h"
class Computer
{
public:
Computer(CPU* cpu, HardDisk* harddisk, Memory* memory);
virtual ~Computer();
void work();
private:
CPU* m_cpu;
HardDisk* m_harddisk;
Memory* m_memory;
};
//CPU.h
#include<iostream>
class CPU
{
public:
CPU() {
}
virtual ~CPU() {
}
virtual void work() =0;
};
//HardDisk.h
#include<iostream>
class HardDisk
{
public:
HardDisk() {
}
virtual ~HardDisk() {
}
virtual void work() =0;
};
//Memory.h
#include<iostream>
class Memory
{
public:
Memory() {
}
virtual ~Memory() {
}
virtual void work() =0;
};
//IntelCPU.h
#include "CPU.h"
class IntelCPU : public CPU
{
public:
IntelCPU();
virtual ~IntelCPU();
virtual void work();
};
//AMDCpu.h
#include "CPU.h"
class AMDCPU : public CPU
{
public:
AMDCPU();
virtual ~AMDCPU();
virtual void work();
};
// WTHardDisk.h
#include "HardDisk.h"
class WTHardDisk : public HardDisk
{
public:
WTHardDisk();
virtual ~WTHardDisk();
virtual void work();
};
//STHardDisk.h
#include "HardDisk.h"
class STHardDisk : public HardDisk
{
public:
STHardDisk();
virtual ~STHardDisk();
virtual void work();
};
//KingstonMemory.h
#include "Memory.h"
class KingstonMemory : public Memory
{
public:
KingstonMemory();
virtual ~KingstonMemory();
virtual void work();
};
//SamsungMemory.h
#include "Memory.h"
class SamsungMemory : public Memory
{
public:
SamsungMemory();
virtual ~SamsungMemory();
virtual void work();
};
//Computer.cpp
#include "Computer.h"
Computer::Computer(CPU* cpu,HardDisk* harddisk,Memory* memory){
m_cpu = cpu;
m_harddisk = harddisk;
m_memory = memory;
}
Computer::~Computer(){
}
void Computer::work(){
m_cpu->work();
m_harddisk->work();
m_memory->work();
}
//IntelCPU.cpp
#include "IntelCPU.h"
IntelCPU::IntelCPU(){
}
IntelCPU::~IntelCPU(){
}
void IntelCPU::work(){
std::cout << "I'm Intel cpu,I'm fine!" << std::endl;
}
//AMDCPU.cpp
#include "AMDCPU.h"
AMDCPU::AMDCPU(){
}
AMDCPU::~AMDCPU(){
}
void AMDCPU::work(){
std::cout << "I'm AMD cpu,I'm fine!" << std::endl;
}
//WTHardDisk.cpp
#include "WTHardDisk.h"
WTHardDisk::WTHardDisk(){
}
WTHardDisk::~WTHardDisk(){
}
void WTHardDisk::work(){
std::cout << "I'm WD Hard Disk,I'm fine!" << std::endl;
}
//STHardDisk.cpp
#include "STHardDisk.h"
STHardDisk::STHardDisk(){
}
STHardDisk::~STHardDisk(){
}
void STHardDisk::work(){
std::cout << "I'm ST Hard Disk,I'm fine!" << std::endl;
}
//KingstonMemory.cpp
#include "KingstonMemory.h"
KingstonMemory::KingstonMemory(){
}
KingstonMemory::~KingstonMemory(){
}
//SamsungMemory.cpp
#include "SamsungMemory.h"
SamsungMemory::SamsungMemory(){
}
SamsungMemory::~SamsungMemory(){
}
void SamsungMemory::work(){
std::cout << "I'm SamsungMemory,I'm fine!" << std::endl;
}
//main.cpp
#include "Computer.h"
#include "WTHardDisk.h"
#include "KingstonMemory.h"
#include "IntelCPU.h"
int main()
{
HardDisk* hard1 = new WTHardDisk;
Memory* mem1 = new KingstonMemory;
CPU* cpu1 = new IntelCPU;
Computer* comp1 = new Computer(cpu1,hard1,mem1);
comp1->work();
return 0;
}
输出:
3.4 依赖倒转原则
依赖倒转原则的核心思想是,上层不要直接依赖于下层,这也不便于维护和扩展。上层和下层都要依赖于抽象或接口,在c++中接口就是抽象类,因此就如上面代码所示,上层和下层代码都依赖于中间的抽象类。
依赖倒转原则实现的代码和策略模式实现的代码高度相似,或者说策略模式符合依赖倒转原则。
4 迪米特法则
迪米特法则(LoD),也叫最小知识原则,含义是:如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要知道另一个类的某一个方法的话,可以通过第三者转发这个调用。
迪米特法则首先强调的前提是在类的结构设计上,每一个类都应当尽量降低成员的访问权限,也就是说,一个类包装好自己的private状态,不需要让别的类知道的字段或行为就不要公开,也就是面向对象设计里的封装。
迪米特法则根本是强调类之间的松耦合。类之间的耦合越松,越有利于复用,一个处在弱耦合中的类被修改,不会对有关系的类造成波及。
如上图所示,一个中介有不同价位的楼盘,那么中介类包含了楼盘类的vector,消费者向中介查询楼盘时,中介可以调用Find接口查看是否还有该类别的楼盘。若有,则返回该楼盘的指针并调用其Sale接口进行销售。
楼盘类和中介类之间的耦合只有一个vector对象和一个Find接口,扩充新的楼盘类,只需要其具有Find接口就不改变中介类的销售规则。两个类之间的耦合较弱,符合迪米特法则。