设计原则的简单理解
前言
好的代码需要有高内聚、低耦合、易扩展且扩展改动小等特点。说实话,我入行很久之后,才知道这些设计原则的名字,但是我并不觉得陌生,反而有一种理所当然的感觉。这得感谢自学时网络上前辈们推荐的书籍,培养了自己的代码洁癖,还得感谢转行后的第一个东家!以下只是我的理解,如有错误,请指正。
单一职责原则
顾名思义,单一职责就是函数或类只做一件事,但是"只做一件事"肯定是针对不同抽象层次的。
举个例子,我要写一个文件,于是我定义了write()
这么一个函数,它确实只做一件事。但是这个write()
对于写一个文件来说是不够的,我肯定还需要open()
和close()
,自然这两个函数也只做了一件事。
但我要真正写一个文件,希望得到的是下面的函数
int write_file() { open(); write(); close(); }
对于write_file()
的调用者来说,write_file()
确实只做了一件事,它帮我写了一个文件。但对于写write_file()
的程序员来说,write_file()
明明做了三件事。
单一职责的职责是对外抽象后的职责,只要函数或类所做的事不超过这个抽象定义,那就不算违背单一职责。
越底层的函数越容易确定职责,甚至只需要这个函数写的足够短小,其职责一般就能保证单一。高层的函数或者类就需要考验程序员的抽象能力了。一个简单的检查该抽象是否能做到单一职责的方法,就是看看函数内是否只做了函数名要求的事,类内所有成员函数和成员变量是否只是为了做到类名要求的事。
当我们长期遵循单一职责原则时,我们会习惯于将一个任务拆分为多个最小步骤,而这通常意味着可以通过流水线,并行等方式来执行这些最小步骤,达到提升性能的目的。
里氏替换
里氏替换就是指子类可以出现在父类出现的任何地方。这是一个指导我们何时使用继承的原则。a继承b,我们说"a是一个b",既然"a是一个b",a自然能替换到b出现的任何地方。如果无法替换的话,说明a不应继承b。
依赖倒置
依赖倒置就是为了做到面向接口编程。
那为什么叫倒置呢?原本高层a直接调用底层b的接口,当高层a想用底层c时就可能需要修改大量代码,原因是c和b是没有约束的,c的接口与b可能完全不一样。为了避免替换底层需要修改大量代码的情况,高层a自己定义了抽象层d,让底层b和底层c都依赖抽象层d的接口去实现。因为抽象层d是高层定义的,而底层b和c是依赖d去实现的,所以就叫依赖倒置。在c++中,这通过定义虚基类,并继承该虚基类来实现。
依赖倒置可以使得高层切换底层实现类时,减少代码修改量,只需要修改一下实例化的代码即可,更灵活。
class d { public: virtual foo1() = 0; ... virtual foon() = 0; } class b : public d; class c : public d; void a() { d* a = new b; // 切换为c时,只需要修改这一行代码。 a->foo1(); ... a->foon(); }
接口隔离原则
接口隔离是针对接口调用者做的隔离,只让调用者看到其能调用的接口,本质上是最小权限。大部分it公司都会宣讲信息安全,而为了保证信息安全的一个原则就是最小化原则。接口隔离就是为了程序安全,如果提供了超过调用者权限的接口,那可能就会引发问题。
迪米特法则
迪米特法则就是最小依赖,依赖更少的类,更少的接口,这样可以降低类之间的耦合和减少代码复杂度。如果某个类依赖了很多类,很多接口,那以后其代码就有更大的可能因为其依赖项的改动而改动,变得不稳定。
开闭原则
对扩展开放,对修改闭合。扩展可以是函数的扩展,也可以是类的扩展,这里的对修改闭合指的是对本层的修改闭合,因为对于高层来说肯定是需要修改的,比如调用新接口,使用新类的实例。之所以对修改闭合,是为了避免修改引入bug。
后话
总是遵顼这些设计原则,对自己的代码能力是有很大益处的。再者设计原则是道,设计模式是术,如果对道理解通透的话,术是可以自行推演的,可能不知不觉中你已经用了某种设计模式了,甚至创造了新的设计模式。