首页 > theory > model > > 正文

再谈设计原则—7种设计原则学习总结笔记

点击:

由领域模型驱动软件设计,用代码来实现该领域模型;领域驱动设计告诉我们,在通过软件实现一个业务系统时,建立一个领域模型是非常重要和必要的,因为领域模型具有以下特点:

这篇文章是对设计模式的再谈系列总结性笔记,推荐阅读C语言中文网上的《软件设计模式概述

什么是设计原则

开闭原则(OPEN CLOSE PRINCIPLE):一个软件实体如类、模块和函数应该对扩展开放,对修改关闭(Software entities should be open for extension,but closed for modification)。

目的就是保证程序的扩展性好,易于维护和升级。开闭原则被称为面向对象设计的基石,实际上,其他原则都可以看作是实现开闭原则的工具和手段。意思就是:软件对扩展应该是开放的,对修改是封闭的,通俗来说就是,开发一个软件时,应该对其进行功能扩展,而在进行这些扩展时,不需要对原来的程序进行修改(不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果)。

单一职责原则(Single-Responsibilitiy Principle)一个类负责一项职责,否则类应该被拆分(There should never be more than one reason for a class to change)

不能存在多于一个导致类变更的原因。举个例子:一个人身兼数职,而这些事情相关性不大,甚至有冲突,那他就无法很好的解决这些问题职责,应该分到不同的人身上去做。

单一职责原则是实现高内聚低耦合的最好方法,没有之一

里氏替换原则(Liskov Substitution Principle):继承必须确保超类所拥有的性质在子类中仍然成立(Inheritance should ensure that any property proved about supertype objects also holds for subtype objects)

继承与派生的规则:子类可以扩展父类的功能,但是不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。任何基类可以出现的地方,子类一定可以出现。它克服了继承中重写父类造成的可复用性变差的缺点。确保类的扩展不会给已有的系统引入新的错误,降低了代码出错的可能性

实现开闭原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。向上转型是Java的基础,我们经常也用到,实际上,在进行设计的时候,尽量从抽象类继承,而不是从具体类继承。同时,保证在软件系统中,把父类都替换成它的子类,程序的行为没有变化,就足够了。

LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

依赖倒置原则(Dependence Inversion Principle):高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象(High level modules shouldnot depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details. Details should depend upon abstractions)。

依赖倒置原则是实现开闭原则的重要途径之一,它降低了客户与实现模块之间的耦合。

其核心思想是:要面向接口编程,不要面向实现编程。即依赖于抽象而不依赖于具体。这个是开闭原则的基础。高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。依赖倒置原则的目的是通过要面向接口的编程来降低类间的耦合性,其需要遵从的规则:

  • 每个类尽量提供接口或抽象类,或者两者都具备。

  • 变量的声明类型尽量是接口或者是抽象类。

  • 任何类都不应该从具体类派生。

  • 使用继承时尽量遵循里氏替换原则。

接口隔离原则(Interface Segregation Principle): 一个类对另一个类的依赖应该建立在最小的接口上(The dependency of one class to another one should depend on the smallest possible interface)。即:建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。该原则还有另外一个定义:客户端不应该被迫依赖于它不使用的方法(Clients should not be forced to depend on methods they do not use)。以上两个定义的含义是:要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。

这个原则的意思是:每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好

接口隔离原则和单一职责都是为了提高类的内聚性、降低它们之间的耦合性,体现了封装的思想,但两者是不同的:

  • 单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。

  • 单一职责原则主要是约束类,它针对的是程序中的实现和细节;接口隔离原则主要约束接口,主要针对抽象和程序整体框架的构建。

在具体应用接口隔离原则时,应该根据以下几个规则来衡量。

  • 接口尽量小,但是要有限度。一个接口只服务于一个子模块或业务逻辑。

  • 为依赖接口的类定制服务。只提供调用者需要的方法,屏蔽不需要的方法。

  • 了解环境,拒绝盲从。每个项目或产品都有选定的环境因素,环境不同,接口拆分的标准就不同深入了解业务逻辑。

  • 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。

迪米特法则||最少知道原则(Low of Demeter Principle||Least Knowledge Principle):只与你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)。其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性(低耦合,高内聚)。

就是说:一个类对自己依赖的类知道的越少越好(黑盒模式)。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。

只与直接的朋友通信(类之间只要有耦合关系,就叫朋友关系)。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。

在运用迪米特法则时要注意以下 6 点。

  • 在类的划分上,应该创建弱耦合的类。类与类之间的耦合越弱,就越有利于实现可复用的目标。

  • 在类的结构设计上,尽量降低类成员的访问权限。

  • 在类的设计上,优先考虑将一个类设置成不变类。

  • 在对其他类的引用上,将引用其他对象的次数降到最低。

  • 不暴露类的属性成员,而应该提供相应的访问器(set 和 get 方法)。

  • 谨慎使用序列化(Serializable)功能

组合||聚合||合成复用原则(Composition/Aggregation Reuse Principle(CARP) )尽量使用组合和聚合,少使用继承的关系来达到复用的原则。如果要使用继承关系,则必须严格遵循里氏替换原则。合成复用原则同里氏替换原则相辅相成的,两者都是开闭原则的具体实现规范。

合成复用原则的重要性

通常类的复用分为继承复用和合成复用两种,继承复用虽然有简单和易实现的优点,但它也存在以下缺点。

  • 继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。

  • 子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。

  • 它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。


采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有以下优点。

  • 它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。

  • 新旧类之间的耦合度低。这种复用所需的依赖较少,新对象存取成分对象的唯一方法是通过成分对象的接口。

  • 复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。