• home > theory > engineering > model >

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

    Author:[email protected] Date:

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

    这篇文章是对设计模式的再谈系列总结性笔记,推荐阅读设计模式 Java版本》、《八大面向对象设计原则》、软件设计模式概述》、

    设计的根本目的是为了解决问题,解决问题的根本前提是要了解到都有哪些问题

    可靠性/健壮性/可修改性/容易理解/程序简便/可测试性/可扩展性/安全性

    软件设计的七大原则

    开闭原则(Open-Closed Principle,OCP):

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

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

    单一职责原则(Single-Responsibilitiy Principle,SRP):

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

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

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

    里氏替换原则(Liskov Substitution Principle,LSP):

    继承必须确保超类所拥有的性质在子类中仍然成立(Inheritance should ensure that any property proved about supertype objects also holds for subtype objects)

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

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

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

    依赖倒置原则(Dependence Inversion Principle,DIP):

    高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象(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,ISP):

    一个类对另一个类的依赖应该建立在最小的接口上(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) ):

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

    合成复用原则的重要性

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

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

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

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

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

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

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

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



    转载本站文章《再谈设计原则—7种设计原则学习总结笔记》,
    请注明出处:https://www.zhoulujun.cn/html/theory/engineering/model/7824.html