单一职责原则 + 开放封闭原则 + 依赖倒转原则 + 迪米特法则

本文深入解析面向对象设计的五大核心原则:单一职责、开放封闭、依赖倒转、里氏代换及迪米特法则,阐述每项原则的定义、重要性及实践案例,帮助开发者构建稳定、灵活的软件系统。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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接口就不改变中介类的销售规则。两个类之间的耦合较弱,符合迪米特法则。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值