下面分享几个软件架构设计时可以遵循的原则,同时在进行功能模块设计也可以参考这些设计原则。
SRP 单一职责原则
- 一个函数只负责完成一个功能
- 任何一个模块只对某一类行为者负责
- 一个类或者函数应该有且仅有一个被改变的理由
在开发过程中,我们要尽可能的将大函数拆解为小函数,小函数各司其事,这样的设计使函数更加灵活,扩展性强。
⚠️ 小函数要精简,但不要过度封装和抽象。
OCP 开闭原则
模块要易于扩展,控制修改。这是我们在初学编程语言时就会被教育到的设计原则。开闭原则帮助我们设计更加灵活的模块,同时还能控制模块变更的影响范围。
LSP 里氏替换原则
所有引用父类的地方都可以替换成子类,而行为不发生改变。
为了保证父类的复用性。它主要是用来判断抽象和继承关系设计是否合理,即某个类是否应该具有某个属性,以及一个类到底是不是另外一个类的子类。
举个栗子🌰:鸵鸟不是鸟
class Bird {
public:
int32_t getVelocity() const {return velocity;}
private:
int32_t velocity = 0; // 飞行速度
};
class Ostrich : public Bird {
};
void crossRiver(Bird bird) {
int32_t distance = 1000;
int32_t elapsed = distance / bird.getVelocity();
}
鸟类Brid具有飞行速度的属性,鸵鸟类Ostrich继承自类Brid,飞行速度默认为0。在函数crossRiver中,将基类Brid对象替换成子类Ostrich对象后,获取的飞行速度为0,出现了除0异常。不符合LSP原则。
so => 鸵鸟不是鸟
所以,里氏替换原则用于验证我们的接口和抽象设计是否合理,同时也可以验证继承关系是否合理。
ISP 接口隔离原则
- 使用接口类的方式细化功能模块,每个接口类负责某一类明确的功能。
- 类似于单一职责原则,多个单一的接口负责的功能更简单,更易于维护,这比一个庞大的接口要好。
- 在做接口设计时要尽量保证接口的小巧、简洁和正交,这样给业务层提供了更多的灵活性。
DIP 依赖反转原则(依赖倒置)
- 1.为了保证系统的灵活性(易于修改)和稳定性(修改影响范围小),在依赖关系中应该避免引用具体的类
- 接口比实现更稳定,所以尽量避免修改函数实现时对依赖该接口的模块的影响
- 继承关系是依赖关系中最强的,尽量避免继承自有具体实现的类
这个原则目的在于降低使模块间的耦合度,并且使底层模块更易于被修改和替换。
总结:以上这五个设计原则统称为SOLID原则。在《整洁架构之道》中有比较详细的介绍。
对于架构设计的学习和理解,我认为很难的一点是:即使懂得很多道理还是很难把事情做好。众多的设计原则都是在不同业务场景下提出的,有些原则之间本身就是矛盾的。无论是架构设计方法还是设计原则,它们不是金科玉律,更不可能放之四海而皆准。它们的价值在于告诉我们应该摒弃什么,应该遵守什么。我们不用那些技术官僚的词汇,用更接地气的描述来说,设计原则也只是要求我们做到简洁、规范和易于理解而已。架构设计并不高端,它本身所产生的价值并不明显,真正能够产生价值的在于我们当前正在走的路:如何理解我们的业务问题。