设计模式六大原则(翻译)
六大原则:
原文链接:https://medium.com/@mena.meseha/6-principles-of-software-design-3a8478954e1c
1 单一职责原则:
定义:
单一职责原则,也被称为单一功能原则。就是说,使类变化的原因不超过一个。通俗地讲,一个类只负责一个功能。
原则:
如果一个类有太多的功能,也就是说有很多功能都聚合到一起,一个功能的改变可能弱化类的其他功能。
这种耦合会导致脆弱的设计,当变化发生时,设计会遭受意外的破坏。如果想避免这种情况的发生,
应该尽可能地遵循单一职责的原则。这个原则的核心是解耦和加强内聚。
问题的起源:
类T负责两个不同的职责:职责P1,职责P2。当类T由于职责P1需要修改时,可能会导致原本正常运行的P2功能出现故障。
也就是说,职责P1和P2是耦合在一起的。
原因:
程序员都知道应该编写出高内聚低耦合的程序,但是很多耦合经常在不经意间发生,因为职责分散。
解决方案:
一个类或者模块只负责一个功能。
优点:
- 可以降低类的复杂性。一个类只负责一个功能,其逻辑肯定比负责多个功能简单得多;
- 提高类的可读性,提高系统的可维护性;
- 变更带来的风险降低了,变更是不可避免的。
- 如果很好地遵循单一职责原则,在修改一个功能时,可以显着减少对其他功能的影响。
2 里氏替换原则(LSP):
定义:
面向对象设计的基本原则之一,任何基类出现的地方都可以被子类替换。
LSP 是继承和重用的基础。只有派生类可以替代基类,不影响软件单元的功能,才能真正复用基类,
派生类也可以在基类的基础上增加新的行为。即如果父类是功能模块的一部分,则使用子类代替父类,功能模块可以正常运行,子类实例也可以代替父类实例
继承的意思是,父类中已经实现的所有方法(相对于抽象方法),实际上都是在设定一系列的规范和约定,
虽然没有强制要求所有的子类都必须遵循这些契约,但是如果子类随意修改这些非抽象的方法,会对整个继承系统造成破坏。
这就是里氏替换原则。
继承作为面向对象的三大特性之一,给编程带来了极大的便利,但也带来了弊端。
例如,使用继承会给程序带来入侵,降低程序的可移植性,增加对象之间的耦合。
如果一个类被其他类继承,则需要修改该类时必须考虑所有子类、类,以及修改父类后,所有涉及子类的函数都可能会失败。
实际情况:
在实际编程中,我们经常通过重写父类的方法来完成新的功能,这样虽然写起来简单,但是整个继承系统的复用性会很差,
尤其是多态使用比较频繁的时候。运行错误的几率非常高。 如果要覆盖父类的方法,
比较常见的做法是: 原来的父类和子类继承一个比较通用的基类,取消原来的继承关系,改为使用依赖、聚合、组合等关系。
总结:
子类可以扩展父类的功能,但不能扩展父类的原有功能。
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
- 子类可以添加自己独特的方法。
- 当子类的方法覆盖父类的方法时,该方法的前提条件(即方法的形参)比父类的输入参数更宽松。
- 当子类的方法实现了父类的抽象方法时,方法的后置条件(即方法的返回值)比父类更严格。
3 依赖倒转原则:
定义:
上层模块不应该依赖下层模块,上层和下层模块都应该依赖它们的抽象;抽象不应该依赖于细节;细节应该依赖于抽象。
区别:
对于面向过程的开发,上层调用下层,上层依赖下层。当下层发生剧烈变化时,上层也需要发生变化,导致模块的复用性降低,大大增加了开发成本。
面向对象的开发很好地解决了这个问题。一般来说,抽象变化的概率很小,用户程序依赖于抽象,实现细节依赖于抽象。即使实现细节不断变化,
只要抽象不改变,客户端程序也不需要改变。这大大降低了客户端程序和实现细节之间的耦合。
问题的根源:
A类直接依赖B类,如果要将A类改成依赖C类,必须通过修改A类的代码来实现。 这种场景下,A类一般是负责复杂业务逻辑的高层模块;
B 类和 C 类是负责基本原子操作的低级模块;如果修改A类,会给程序带来不必要的风险。
解决方案:
修改A类依赖接口I。B类和C类各自实现接口I。A类通过接口I间接与B类或C类通信,大大减少了修改A类的机会。
依赖倒置的原理是基于这样一个事实,即抽象事物(接口或抽象类)比细节的可变性要稳定得多。建立在抽象上的架构比建立在细节(特定的实现类)上的架构要稳定得多。
传递依赖的方式有接口传递、构造函数传递、设置方法传递三种方式
编码要求:
- 低级模块应该有抽象类或接口,或者两者都有。
- 变量的声明类型尽可能是抽象类或接口。
- 使用继承时遵循里氏置换原则。
4 接口隔离原则ISP
定义:
客户端不应依赖它不需要的接口;一个类对另一个类的依赖性应基于最小的接口。
问题的根源:
A 类通过接口 I 依赖于类 B,C 类通过接口 I 依赖于 D 类。如果接口 I 不是类 A 和类 B 的最小接口,则类 B 和类 D 必须实现它们不需要的方法。
解决方案:
将臃肿的接口 I 拆分为单独的接口,A 类和 C 类分别与它们所需的接口建立依赖关系,接口隔离原理。
因此:
创建单一的界面,不要构建大而臃肿的界面,尽量细化界面,界面中的方法尽可能小。也就是说,我们需要为每个类创建一个专用接口,而不是尝试为依赖于它的所有类构建一个非常大的接口。
注意:
- 接口功能尽可能少,细化接口可以提高编程的灵活性,但如果接口太小,它会导致太多的接口并使设计复杂化,所以一定要适度。
- 为依赖于接口的类自定义服务仅向调用类公开它需要的方法,并且它不需要的方法被隐藏。只有专注于为模块提供自定义服务,才能建立最小的依赖关系。
- 提高凝聚力并减少外部互动,使接口用最少的方法做最多的事情。
5. 最小知道原则
定义:
这意味着一个物体应该对其他物体有尽可能少的知识,并且不与陌生人交谈,英语缩写为:LOD。
通俗地说,一个类对它所依赖的类了解得越少越好。也就是说,对于依赖类,无论逻辑多么复杂,逻辑都尽可能地封装在类中,外部提供的公共方法不会泄露任何信息。另一种说法:只与直接的朋友沟通
直接朋友:
成员变量中的类、方法参数、方法返回值、
间接朋友:
局部变量中的类
问题的根源:
类与类之间的关系越密切,耦合程度越高。当一个类更改时,对另一个类的影响更大。
解决方法:
尝试减少类和类之间的耦合。
注意:
迪米特定律的初衷是减少类之间的耦合。由于每个类都减少了不必要的依赖关系,
因此它确实可以减少耦合关系。但凡事都有一定程度的,虽然可以避免与间接类的交流,但要进行交流,势必要通过"中介"来连接。
过度使用Dimit原则将导致大量此类中介和交付类,从而导致系统复杂性增加。
因此,在使用Dimitte规则时,我们必须反复权衡天平,使结构清晰,同时又具有高内聚性和低耦合性。
6. 开-闭原理
定义:
软件实体(如类、模块和函数)应开放给扩展,不开放给修改。
问题的根源:
在软件生命周期中,当软件的原始代码由于更改、升级和维护而需要修改时,可能会在旧代码中引入错误,或者可能导致我们重构整个功能。存在已重新测试的代码。
解决方法:
当需要更改时,请尝试通过扩展来实现更改,而不是修改现有代码来实现更改。
总结一下:
使用抽象生成框架通过实现扩展详细信息。 因为抽象的灵活性好,适应性广,只要抽象合理,软件架构基本可以保持稳定。
而软件中的变量细节,我们用抽象派生的实现类来扩展,当软件需要改变的时候,我们只需要重新派生一个实现类来根据需求进行扩展。
当然,前提是我们的抽象应该是合理的,我们必须前瞻性地预测需求的变化。

浙公网安备 33010602011771号