七大程序设计原则简介

在程序设计中我们要遵循一些原则,来降低对象之间的耦合性,增加代码的可复用性、可扩展性、可维护性。常见的程序设计原则有以下七种:

1、单一职责原则 Single Responsibility Principle
2、开闭原则 Open-Closed Principle
3、迪米特法则/最少知识原则 Law of Demeter/The Least Knowledge Principle
4、依赖倒置原则 Dependency Inversion Principle
5、合成复用原则 Composite Reuse Principle
6、接口隔离原则 Interface Segregation Principle
7、里氏替换原则 Liskov Substitution Principle

一、单一职责原则

一个类应该承担单一的职责(功能),不应包含过多的功能。若一个类有多个职责,某个单一职责的变化可能会削弱或抑制该类实现其它职责的能力。此外,若我们只需要用到该类的其中一个职责,不得不将其它不需要的职责包含进来,造成代码冗余。

可以降低类的复杂性、提高代码的可读性和可维护性、降低改变代码带来的风险。

总结:单一职责原则帮助我们避免实现大而全的类,避免不相关功能塞在一个类中;同时能减少所需要的依赖和被依赖的类。

二、开闭原则

对扩展开放(应对变化),对修改关闭(保持稳定性)。当需求发生变化时,我们应在不修改已有代码的基础上扩展功能。若每次软件需要被改动时都要修改旧代码,是很有风险的,牵一发而动全身。

遵循开闭原则扩展后,我们只需要对扩展后的代码进行测试即可,能提高代码的可复用性和稳定性。

总结:实际开发中我们应尽量用抽象构建框架、用实现扩展细节。对于函数的参数和引用对象中尽量使用抽象类,这样万一需求有变化我们只要改动实现即可。抽象层尽量保持稳定,接口和抽象类只负责定义方法不负责实现。

三、迪米特法则

一个类对于其它类知道的越少越好,方法只和直接朋友(方法所在类的成员变量、方法参数、返回值中的类)通信。如果两个类彼此不是直接朋友,就不应该发生直接的相互作用(比如陌生的类不应该作为局部变量出现在类的内部)。如果确有需要,可以通过中间层转发。

遵守迪米特法则可以降低耦合性、提高系统模块功能的独立性。

总结:不要在实际开发中出现诸如 objectA.getObjectB().doSomething() 的代码。遵守迪米特法则会产生一定数量的中介类,也可能会提高系统的复杂度。

四、依赖倒置原则

高层模块不应该依赖低层模块的具体实现,而应该基于抽象,低层亦然。换句话说,具体类之间不要发生直接的依赖关系,应该通过接口或抽象类来联系。另外,接口和抽象类不应该依赖于具体类,而具体类要依赖于接口或抽象类(以实现抽象),即:

高层模块 → 接口/抽象类 ← 低层模块

若没有中间的抽象,当低层代码剧烈变动时,高层也要变动,降低了模块复用性、提高了开发和维护的成本。引入抽象后,只要抽象不变,无论低层如何变动,高层都不用变化,大大降低了高层模块和低层实现细节的耦合度。这样系统架构就更稳定、能更好的应对需求变化。

总结:程序应依赖抽象的接口,而不应依赖于具体实现,因为抽象是相对稳定的,细节是多变的。一个典型遵循依赖倒置的设计模式就是工厂方法。

五、合成复用原则

尽量使用对象的聚合/组合,而不是继承关系来达到代码复用的目的。

聚合关系:整体和部分在生命周期上没有必然联系,部分可以在整体创建之前创建,也可以在整体销毁后再销毁。如公司与销售员、工程师、会计师的关系就是聚合关系。

组合关系:部分不能独立于整体存在,整体生命周期结束也意味着部分生命周期的结束。如生物体和各个器官的关系就是组合关系。

继承关系:从已有的抽象类中派生出新的类,是从抽象到具体的关系,新的类能吸收已有类数据的属性和行为,并扩展出新的能力。如动物和猫、狗之间的关系。

继承复用虽然简单、易于实现,但是父类的实现细节会暴露给子类,破坏了封装(父类对子类是透明的)。同时,父类的任何改变都会导致子类的实现发生变化,不利于类的扩展和维护,也限制了灵活性。合成复用将已有的对象纳入新对象中,新对象可以调用已有对象的功能,这种方式维持了类的封装性。

总结:组合/聚合 > 继承,但是注意同时不要肆无忌惮,否则会违背大量的其他原则,对维护和可读性造成较大的不良影响。

六、接口隔离原则

接口隔离原则有以下两方面:

1、客户端不应依赖它不使用的接口(低耦合)。如果客户端依赖了不必要的接口,就要面临接口变动带来的风险,所以客户端不应被迫使用对其无用的方法或功能。
2、每个接口承担的职责应该是单一的(高内聚)。不要把没有关系的接口合并到一起去,这是对功能分配和接口的污染,应把庞大臃肿的接口拆分成更小更具体的接口。

这和单一职责原则很像,不同的是单一职责原则针对的是模块、类、接口的设计,注重的是职责,是业务逻辑上的划分。而接口隔离原则相对于单一职责原则,更侧重于约束接口的设计。单一职责原则更精细,而接口隔离原则更注重相似接口的隔离。

接口隔离原则提供了一种判断接口的职责是否单一的标准:通过调用者如何使用接口来间接地判定。如果调用者只使用部分接口或接口的部分功能,那么接口的设计就比较臃肿,违背了接口隔离原则。

总结:多个专门的接口 > 单一的总接口,不过接口设计粒度太小也可能适得其反。通常来说,一个接口只服务于一个子模块或业务逻辑是比较好的。

七、里氏替换原则

任何基类可以出现的地方,子类一定可以出现(替换)。这就要求基类拥有的性质在子类中须仍然成立;子类在扩展父类功能的同时,不改变父类原有的功能。遵循里氏替换原则才能保证基类的复用性、降低系统出错率。

规范遵循里氏替换原则需要注意以下几点:
1、子类必须完全实现父类的抽象方法,但不能覆盖父类的非抽象方法;
2、子类可以实现自身特有的方法;
3、子类对象实现父类的抽象方法时,方法的返回值要与父类一致或更严格;
4、子类对象可以替代任何父类对象,但反过来不成立。

总结:里氏替换原则 = 父类能被子类替换 = 继承复用的规范,虽然不遵守规范可能当前没问题,但后续出错率会提高。使用继承会给程序带来侵入性、降低程序的可移植性、增加对象间的耦合性。虽然遵循里氏替换原则能缓解上面的副作用,但还是推荐使用聚合/组合关系。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注