【再探】设计模式-设计原则

设计原则是在编写程序时引导程序员遵循的一些原则和准则。这些原则旨在提高代码的可读性、可维护性、可扩展性和可重用性。

  1. 可读性:理解和沟通的难易程度。
  2. 可维护性:修改和调整的难易程度。
  3. 可扩展性:应对未来变化的能力。
  4. 可重用性:资产的复用程度。

1 系统质量的衡量指标

1.1 高内聚低耦合

1.1.1 内聚

又称内联系,指模块内部各个元素(字段、方法等)彼此结合的紧密程度的度量。联系越紧密,则内聚性越高。

低内聚下,模块的功能可能不单一,模块的职责也不明确、可读性、扩展性很差。

偶然内聚

最低级别内聚。各成分之间没有关联,只是把分散的功能合并在一起。

逻辑内聚

执行功能上相似,可能会有些差异,但它们在逻辑上属于同一类。

例如鼠标和键盘,它们都属于输入模块。

时间内聚

这些方法需要在同一时间段内完成。

过程内聚

模块内各个元素都是为了完成同一个过程(可能并不直接相关于同一功能)而存在的,并按特定的顺序执行。

这使得代码结构更加清晰。

通信内聚

模块内元素通过共享数据或信息进行交流和协作,彼此依赖于共享的数据或信息来完成其功能。

顺序内聚

模块内元素不仅密切相关于同一功能,必须按照特定的顺序执行,美国处理元素的输出作为下一个处理元素的输入。

这确保来数据在模块内部按照预期的方式流动,从而实现了功能的连贯性和完整性,简化了代码的理解和维护,可能会导致模块间的耦合度较高。

功能内聚

模块内所以元素共同完成一个功能,缺一不可,且模块不能再切割。

表 七种内聚类型

1.1.2 耦合

模块之间的依赖关系越紧密,耦合度越高。修改一个模块就可能会影响到其他多个模块。

内容耦合

耦合度最大的一种形式,也被认为“病态耦合”。

一个模块不经调用直接使用另一个模块的内部数据或代码。有以下情形:

  1. 一个模块之间访问另一个模块的内部数据。
  2. 一个模块不通过正常入口进入到另一个模块内部。
  3. 两个模块有一部分代码重叠。
  4. 一个模块有多个入口。

公共耦合

不同模块引用了同一个全局数据。

外部耦合

模块与软件系统外部联系。例如数据库、硬件设备等。

控制耦合

一个模块调用另一个模块时,传递的是控制变量,被调用的模块会根据这个变量选择性地执行其内部功能。

标记耦合

两个模块之间通过参数传递对象,该对象的字段可以被视为标记。因为两个模块都可以对这个对象标记进行修改,因此耦合度相当较高。

数据耦合

耦合程度最低的一种类型,两个模块之间通过基本类型参数进行交互。

非直接耦合

模块之间没有直接相互调用关系,而是通过接口或者事件的方式进行通信。耦合度较低。

表 七种耦合类型

2 七大设计原则

2.1 单一职责

每个类应该只有一个职责,仅负责一个功能领域中的相应职责。

2.1.1 提出背景

当一个类职责过多时有以下缺点:

  1. 可读性差,类变得臃肿。
  2. 耦合度高,类与其他的类可能存在联系,当修改其功能时,可能会影响其他模块。
  3. 可维护性差,当修改其中一个职责时,可能会影响其他职责的运行。

2.1.2 优缺点

优点

  1. 提高类代码的可读性和可维护性。只负责一个特定职责,因此代码结构明了,易于理解和维护。
  2. 降低耦合度。类之间的关联和依赖减少,使得代码更加模块化和可重用。

缺点

  1. 类的数量增多,增加代码的复杂性。
  2. 过度拆分可能会降低性能。会导致在运行时需要创建很多的对象或进行更多的函数调用。

表 单一职责的优缺点

2.1.3 合理使用

1)识别并区分职责,确保每个类或方法只负责一个职责,并且这个职责时清晰明确的。

2)避免过度拆分,根据实际情况和需求来平衡类的数量和职责的划分。

2.2 开闭原则

是面向对象设计中最基础的设计原则,是接下来5种设计原则的基础。

主张一个软件实体(类、模块、函数等)应当对外扩展开发,对修改关闭。

核心思想是将可能变化的部分与不变的部分隔离开来,把变化的部分封装起来,而不变的则抽象为接口(面向接口编程)。

2.2.1 提出背景

不遵循开闭原则有以下缺点:

  1. 可维护性差,任何小的改动可能要直接修改现有代码,这不仅增加了出错的概率,也使得代码库变得越来越难易理解和维护。
  2. 可扩展性差,当需要添加新功能时,可能需要修改多个类或方法,可能会导致代码冗余和重复。
  3. 耦合度高,同时增加了测试难度和成本。
  4. 破坏封装性,可能会导致内部实现细节暴露给外部调用者。

2.2.2 优缺点

优点

  1. 可维护性好,所有的功能和特性都是基于抽象框架扩展而成,这使得各个功能特性之间保持独立且互不影响,后期维护目标明确。
  2. 扩展性好,可在不修改原有代码的基础上,基于抽象框架进行扩展。
  3. 可复用性好,抽象出来的部分具有共同特性,可以之间复用。

缺点

1)抽象难度较大,如果在初始阶段使用抽象构建的框架考虑不够全面,后期已扩展了很多功能特性时,一旦抽象框架发生变动,可能会影响下面的扩展部分。

表 开闭原则的优缺点

2.2.3 合理使用

1)明确抽象和具体实现,即区分变与不变的部分,将不变的部分抽象为接口或抽象类,而将变化的部分作为具体实现。

2)遵循单一职责原则,有助于识别并隔离可能变化的部分。

3)优先使用组合而非继承,通过组合,可以将对象视为黑盒子,只关心其提供的接口,而不必深入了解其内部实现。

2.3 里氏替换原则

子类必须能够替换其基类,且不会导致任何不期望的行为或错误。

子类可以扩展父类的功能,但不能改变其原有功能。不应该覆盖其非抽象方法,同时子类可以增加自己特有的方法。

2.3.1 提出背景

不遵循里氏替换原则的缺点:

  1. 破坏多态性,子类可能无法正确替换父类,限制了代码的灵活性和重用性。
  2. 破坏了封装性,使得父类的内部实现细节暴露给外部调用者。
  3. 可维护性差,开发者需要仔细跟踪每个子类对父类方法对覆盖情况,以确保它们的行为符合预期。
  4. 扩展性差,当需要添加新的功能或修改现有功能时,可能需要直接修改父类。

2.3.2 优缺点

优点

  1. 可维护性好,使得代码更模块化,有助于减少运行时错误。
  2. 可扩展性好,当需要修改或扩展时,可以通过添加新的子类来实现。
  3. 可复用性好,可以重用分类代码。

缺点

  1. 增加了设计的复杂性,需要对类的继承关系进行仔细的规划和设计,需要更多的时间来确保子类能正确地替换父类,并不会引入错误。
  2. 限制创新的灵活性,因为子类必须遵循父类的契约,可能需要放弃一些创新的想法和设计。
  3. 可能导致过度设计,可能会过度设计类的结构和关系,导致代码变得过于复杂和难以理解,会增加开发和维护的成本。

表 里氏替换原则的优缺点

2.3.3 合理使用

1)正确理解和使用继承,应当谨慎使用继承,避免滥用。过度使用可能会导致代码结构混乱,难以维护。

2)遵循契约设计,确保父类定义了一个明确的契约,即其方法和属性的预期行为和约束。如果子类需要改变父类的行为,应当考虑新的方法或属性,而不是直接覆盖父类的方法。

2.4 依赖倒转原则

高层模块不应该依赖低层模块,二者都应该依赖低层模块的抽象;抽象不应该依赖细节,细节应当依赖于抽象。

在代码中传递参数或搭建关联关系时,尽量使用接口和抽象类来代替具体类来进行变量类型声明、参数类型声明、方法返回类型声明等。

2.4.1 不遵循依赖倒转原则的缺点

1)可维护性差,高层模块依赖低层模块细节,如果底层模块细节做了修改,可能会影响其他模块。

2)可扩展性差,当添加新功能或支持新业务逻辑时,因为高层模块直接依赖底层模块的具体细节,这让扩展变得非常困难。

3)增加测试难度,模块之间的耦合度很高,当测试一个高层模块时,可能需要同时模拟和测试多个底层模块。

4)可重用性差,代码中可能会包含大量的具体实现细节,使得它难以在不同的场景或项目中进行重用。

2.4.2 优缺点

优点

  1. 降低耦合度,当底层模块发生变化时,高层模块不需要修改,只要底层模块仍符合抽象层的约定即可。
  2. 可维护性好,当需要修改或替换底层模块时,只需确保新的实现符合抽象层的接口即可,无需修改其他模块。
  3. 可扩展性好,只需实现新的抽象接口,就可将新功能集成到系统中。
  4. 重用性好,可以使得多个底层模块实现相同的接口。

缺点

  1. 增加抽象层,需要定义很多的接口或抽象类。
  2. 过度抽象,过度使用依赖倒转原则,会导致过多的抽象层,使得代码难以理解和维护。
  3. 性能影响,引入抽象层,可能在一定程度上影响程序的性能,尤其是需要频繁进行接口调用或类型转换的场景中。

图 依赖倒转原则优缺点

2.4.3 合理使用

1)识别并定义抽象,识别出系统哪些部分可以抽象化。

2)让高层模块依赖于抽象,通过抽象进行通信。

3)低层模块实现抽象,实现的细节应该隐藏在低层模块中。

4)避免过度抽象。

5)使用依赖注入等技术,在运行时将具体的实现类注入到需要它的高层模块中,从而实现高层模块与底层模块之间的解耦,容器管理依赖。

2.5 接口隔离原则

一个类对于另一个类的依赖应该建立在最小的接口上。(为各个类建立它们需要的专用接口,接口方法尽量细化)

2.5.1 不遵守接口隔离规则的缺点

1)可读性差,接口过于臃肿导致实现类的代码量增多。

2)可维护性差,内聚性可能比较松散,修改一个方法可能会影响其他方法。

3)耦合性高,与多个模块会相关联。一旦接口发生变更,所有依赖于该接口的客户端都可能需要进行相应修改。

4)可扩展性差,当需要扩展功能时,需要花费更多时间和精力来分析接口的结构和行为。

2.5.2 优缺点

优点

  1. 降低耦合度,通过细分接口,可以减少模块之间的依赖关系,从而降低系统整体的耦合度。
  2. 可扩展性及可维护性好,当需要添加新功能或修改现有功能时,可以通过定义新的接口或修改现有接口来实现,而不需要对整个系统进行大规模的修改。
  3. 可读性好,代码更加清晰、简洁。

缺点

  1. 接口数量增加,可能需要定义更多的接口。
  2. 过度拆分风险,导致接口过于细碎,增加系统的复杂性。也会使开发人员管理更多的接口,增加开发和维护的难度。

表 接口隔离原则的优缺点

2.5.3 合理使用

1)分析现有接口,拆分过于庞大或臃肿的接口,识别出接口中不同客户端可能不需要的方法。

2)保持接口独立性,美国小接口独立存在,不依赖其他接口的实现。

3)适度拆分,避免过度拆分导致数量过多,权衡接口拆分程度和系统的整体复杂性。

2.6 合成复用原则

尽量使用对象组合而不是继承来达到复用的目的。

2.6.1 不遵循合成复用原则的缺点

1)过度依赖继承,导致类层次结构变得庞大而复杂,使得理解和维护代码变得困难。

2)降低代码灵活性,继承意味着子类必须遵循基类的设计和实现,这限制了子类的灵活性和创新空间。

3)耦合度高,当基类发生变化时,所有子类都可能受到影响。

2.6.2 优缺点

优点

  1. 提高类灵活性,组合关系运行时可以动态地改变对象的行为,增加了系统的动态性。
  2. 降低耦合度,减少了类之间的依赖关系,对象之间通过接口或委托进行交互。
  3. 提高代码的复用性,通过复用已有的对象来构建新的对象。
  4. 可读性强,通过组合,可以将复杂的功能拆分成多个简单的对象,使得每个对象的功能更加单一和明确。

缺点

  1. 性能开销,组合可能需要更多的运行时开销,因为每个对象都需要吧单独创建和管理。
  2. 可能会增加系统复杂性,过度使用会导致结构变得复杂,包含大量的对象和交互关系。

表 合成复用原则的优缺点

2.6.3 合理使用

1)优先考虑使用组合而非继承关系来实现代码复用。

2)对象之间通过接口来引用。

3)适度复用,避免引入不必要的复杂性和性能开销。

2.7 迪米特法则

也成为最小知识原则。

一个对象对其他对象保持最少的了解,只需要知道与自己密切相关的类。

与自己密切相关的类包括:

  1. 当前对象本身。
  2. 以参数形式传入到当前对象方法中的对象。
  3. 当前对象的成员对象。
  4. 当前对象所创建的对象。

2.7.1 不遵循迪米特法则的缺点

1)高耦合低内聚,当一个类知道太多其他类的细节时,这些类之间就形成了紧密的耦合关系。同时它的内部功能就不再集中和一致,职责变得模糊。

2)性能下降,过多的类间通信和依赖关系导致性能下降,美国通信和依赖都可能引入额外的开销。

3)违反封装原则。封装强调对象的内部状态和行为隐藏起来,如果一个类知道其他类太多细节,那封装就被破坏了。

2.7.2 优缺点

优点

  1. 降低耦合度。
  2. 提高可扩展性。
  3. 促进代码重用。

缺点

  1. 增加通信开销。
  2. 设计难度增加。
  3. 可能过度设计。

表 迪米特法则的优缺点

2.7.3 合理使用

1)明确职责和接口,避免暴露过多的内部状态和方法,以减少外部类对其依赖。

2)减少直接依赖,通过中介者、代理或事件进行通信。

3)考虑性能和效率

相关推荐

  1. 设计模式-设计原则

    2024-04-21 11:16:04       37 阅读
  2. 设计模式设计原则

    2024-04-21 11:16:04       27 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-21 11:16:04       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-21 11:16:04       100 阅读
  3. 在Django里面运行非项目文件

    2024-04-21 11:16:04       82 阅读
  4. Python语言-面向对象

    2024-04-21 11:16:04       91 阅读

热门阅读

  1. grep常规用法整理【ing】

    2024-04-21 11:16:04       34 阅读
  2. 【计算机网络】面经

    2024-04-21 11:16:04       38 阅读
  3. 如何使用Python进行Web开发,如Flask或Django?

    2024-04-21 11:16:04       37 阅读
  4. 华为海思数字芯片设计笔试第八套

    2024-04-21 11:16:04       28 阅读
  5. LlamaIndex 组件 - Prompts

    2024-04-21 11:16:04       49 阅读
  6. 汽车牌照-C++

    2024-04-21 11:16:04       26 阅读
  7. 什么是Transformer架构的自注意力机制?

    2024-04-21 11:16:04       37 阅读
  8. HTML5声明与编码设置

    2024-04-21 11:16:04       35 阅读