(3)设计模式

编程入门 行业动态 更新时间:2024-10-24 14:25:04

(3)设计<a href=https://www.elefans.com/category/jswz/34/1771241.html style=模式"/>

(3)设计模式

文章目录

    • 1. 单一职责原则(SRP)
    • 2. 开闭原则(OCP)
    • 3. 里式替换原则(LSP)
    • 4. 接口隔离原则(ISP)
    • 5. 依赖反转(DIP)
      • 5.1 控制反转(IOC)
      • 5.2 依赖注入(DI)
      • 5.3 依赖注入框架
      • 5.4 依赖反转原则(DIP)
    • 6. KISS原则 和 YAGNI原则
      • 6.1 KISS原则
      • 6.2 YAGNI原则
    • 7. DRY原则
      • 7.1 DRY 原则
      • 7.2 代码复用性
    • 8. 迪米特法则(LOD)
      • 8.1 高内聚、松耦合
      • 8.2 迪米特法则

本文包括的设计原则有:SOLID、KISS、YAGNI、DRY、LOD 。
SOLID原则分别是:单一职责原则、开闭原则、里式替换原则、接口隔离原则和依赖反转原则。

1. 单一职责原则(SRP)

单一职责原则的英文是 Single Responsibility Principle,缩写为 SRP。一个类或者模块只负责完成一个职责(或者功能)。也就是说,不要设计大而全的类,要设计粒度小、功能单一的类。

如何判断类的职责是否足够单一

  • 类中的代码行数、函数或属性过多,会影响代码的可读性和可维护性,就需要考虑对类进行拆分;
  • 类依赖的其他类过多,或者依赖类的其他类过多,不符合高内聚、低耦合的设计思想,就需要考虑对类进行拆分;
  • 私有方法过多,就要考虑能否将私有方法独立到新的类中,设置为 public 方法,供更多的类使用,从而提高代码的复用性;
  • 比较难给类起一个合适名字,很难用一个业务名词概括,或者只能用一些笼统的 Manager、Context 之类的词语来命名,这就说明类的职责定义得可能不够清晰;
  • 类中大量的方法都是集中操作类中的某几个属性,比如,在 UserInfo 中,如果一半的方法都是在操作 address 信息,那就可以考虑将这几个属性和对应的方法拆分出来。

从另一个角度来看,当一个类的代码,读起来让你头大了,实现某个功能时不知道该用哪个函数了,想用哪个函数翻半天都找不到了,只用到一个小功能要引入整个类(类中包含很多无关此功能实现的函数)的时候,这就说明类的行数、函数、属性过多了。职责不单一。

但是,不同的应用场景、不同阶段的需求背景下,对同一个类的职责是否单一的判定,可能都是不一样的。可以先写一个粗粒度的类,满足业务需求。随着业务的发展,如果粗粒度的类越来越庞大,代码越来越多,这个时候,就可以将这个粗粒度的类,拆分成几个更细粒度的类。这就是所谓的持续重构

类的职责是否设计得越单一越好?
单一职责原则通过避免设计大而全的类,避免将不相关的功能耦合在一起,来提高类的内聚性。同时,类职责单一,类依赖的和被依赖的其他类也会变少,减少了代码的耦合性,以此来实现代码的高内聚、低耦合。但是,如果拆分得过细,实际上会适得其反,反倒会降低内聚性,也会影响代码的可维护性。

2. 开闭原则(OCP)

软件实体(模块、类、方法等)应该“对扩展开放、对修改关闭”。软件实体(模块、类、方法等)应该“对扩展开放、对修改关闭”。

同样一个代码改动,在粗代码粒度下,被认定为“修改”,在细代码粒度下,又可以被认定为“扩展”。

改动代码时,只要它没有破坏原有的代码的正常运行,没有破坏原有的单元测试,就可以说,这是一个合格的代码改动。

让修改操作更集中、更少、更上层,尽量让最核心、最复杂的那部分逻辑代码满足开闭原则。

时刻具备扩展意识、抽象意识、封装意识。

最常用来提高代码扩展性的方法有:多态、依赖注入、基于接口而非实现编程,以及大部分的设计模式(比如,装饰、策略、模板、职责链、状态等)。

代码的扩展性会跟可读性相冲突。

3. 里式替换原则(LSP)

子类对象(object of subtype/derived class)能够替换程序(program)中父类对象(object of base/parent class)出现的任何地方,并且保证原来程序的逻辑行为(behavior)不变及正确性不被破坏。

多态和里式替换有点类似,但它们关注的角度是不一样的。多态是面向对象编程的一大特性,也是面向对象编程语言的一种语法。它是一种代码实现的思路。而里式替换是一种设计原则,是用来指导继承关系中子类该如何设计的,子类的设计要保证在替换父类的时候,不改变原有程序的逻辑以及不破坏原有程序的正确性。

子类在设计的时候,要遵守父类的行为约定(或者叫协议)。父类定义了函数的行为约定,那子类可以改变函数的内部实现逻辑,但不能改变函数原有的行为约定。这里的行为约定包括:函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明。

违反里式替换原则的例子

  1. 子类违背父类声明要实现的功能。
  2. 子类违背父类对输入、输出、异常的约定。
  3. 子类违背父类注释中所罗列的任何特殊说明。

拿父类的单元测试去验证子类的代码。如果某些单元测试运行失败,就有可能说明,子类的设计实现没有完全地遵守父类的约定,子类有可能违背了里式替换原则。

里式替换原则是用来指导,继承关系中子类该如何设计的一个原则。理解里式替换原则,最核心的就是理解“design by contract,按照协议来设计”这几个字。父类定义了函数的“约定”(或者叫协议),那子类可以改变函数的内部实现逻辑,但不能改变函数原有的“约定”。这里的约定包括:函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明。多态是面向对象编程的一大特性,也是面向对象编程语言的一种语法。它是一种代码实现的思路。而里式替换是一种设计原则,用来指导继承关系中子类该如何设计,子类的设计要保证在替换父类的时候,不改变原有程序的逻辑及不破坏原有程序的正确性。

4. 接口隔离原则(ISP)

客户端不应该被强迫依赖它不需要的接口。

把“接口”理解为下面三种东西:

  1. 一组 API 接口集合。
  2. 单个 API 接口或函数。
  3. OOP 中的接口概念。

如果把“接口”理解为一组接口集合,可以是某个微服务的接口,也可以是某个类库的接口等。如果部分接口只被部分调用者使用,我们就需要将这部分接口隔离出来,单独给这部分调用者使用,而不强迫其他调用者也依赖这部分不会被用到的接口。

如果把“接口”理解为单个 API 接口或函数,部分调用者只需要函数中的部分功能,那我们就需要把函数拆分成粒度更细的多个函数,让调用者只依赖它需要的那个细粒度函数。

如果把“接口”理解为 OOP 中的接口,也可以理解为面向对象编程语言中的接口语法。那接口的设计要尽量单一,不要让接口的实现类和调用者,依赖不需要的接口函数。

接口隔离原则与单一职责原则的区别
单一职责原则针对的是模块、类、接口的设计。接口隔离原则相对于单一职责原则,一方面更侧重于接口的设计,另一方面它的思考角度也是不同的。接口隔离原则提供了一种判断接口的职责是否单一的标准:通过调用者如何使用接口来间接地判定。如果调用者只使用部分接口或接口的部分功能,那接口的设计就不够职责单一。

5. 依赖反转(DIP)

5.1 控制反转(IOC)

实际上,控制反转是一个比较笼统的设计思想,并不是一种具体的实现方法,一般用来指导框架层面的设计。这里所说的“控制”指的是对程序执行流程的控制,而“反转”指的是在没有使用框架之前,程序员自己控制整个程序的执行。在使用框架之后,整个程序的执行流程通过框架来控制。流程的控制权从程序员“反转”给了框架。

5.2 依赖注入(DI)

依赖注入和控制反转恰恰相反,它是一种具体的编码技巧。不通过 new 的方式在类内部创建依赖类的对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类来使用。

5.3 依赖注入框架

通过依赖注入框架提供的扩展点,简单配置一下所有需要的类及其类与类之间依赖关系,就可以实现由框架来自动创建对象、管理对象的生命周期、依赖注入等原本需要程序员来做的事情。

5.4 依赖反转原则(DIP)

依赖反转原则。依赖反转原则的英文翻译是 Dependency Inversion Principle,缩写为 DIP。中文翻译有时候也叫依赖倒置原则。

高层模块(high-level modules)不要依赖低层模块(low-level)。高层模块和低层模块应该通过抽象(abstractions)来互相依赖。除此之外,抽象(abstractions)不要依赖具体实现细节(details),具体实现细节(details)依赖抽象(abstractions)。

这条原则跟控制反转有点类似,主要用来指导框架层面的设计。高层模块不依赖低层模块,它们共同依赖同一个抽象。抽象不要依赖具体实现细节,具体实现细节依赖抽象。

6. KISS原则 和 YAGNI原则

6.1 KISS原则

尽量保持简单。

KISS 原则是保持代码可读和可维护的重要手段。KISS 原则中的“简单”并不是以代码行数来考量的。代码行数越少并不代表代码越简单,还要考虑逻辑复杂度、实现难度、代码的可读性等。而且,本身就复杂的问题,用复杂的方法解决,并不违背 KISS 原则。除此之外,同样的代码,在某个业务场景下满足 KISS 原则,换一个应用场景可能就不满足了。

  • 不要使用同事可能不懂的技术来实现代码。
  • 不要重复造轮子,要善于使用已经有的工具类库。
  • 不要过度优化。不要过度使用一些奇技淫巧(比如,位运算代替算术运算、复杂的条件语句代替 if-else、使用一些过于底层的函数等)来优化代码,牺牲代码的可读性。

6.2 YAGNI原则

YAGNI 原则的英文全称是:You Ain’t Gonna Need It。

不要去设计当前用不到的功能;不要去编写当前用不到的代码。实际上,这条原则的核心思想就是:不要做过度设计。

KISS 原则讲的是“如何做”的问题(尽量保持简单),而 YAGNI 原则说的是“要不要做”的问题(当前不需要的就不要做)。

7. DRY原则

英文描述为:Don’t Repeat Yourself。中文直译为:不要重复自己。可以理解为:不要写重复的代码。

只要两段代码长得一样,那就是违反 DRY 原则了。真的是这样吗?答案是否定的。

7.1 DRY 原则

三种代码重复的情况:实现逻辑重复、功能语义重复、代码执行重复。实现逻辑重复,但功能语义不重复的代码,并不违反 DRY 原则。实现逻辑不重复,但功能语义重复的代码,也算是违反 DRY 原则。除此之外,代码执行重复也算是违反 DRY 原则。

所谓“语义不重复”指的是:从功能上来看,这两个函数干的是完全不重复的两件事情。语义重复,也就是功能重复。

7.2 代码复用性

代码复用性(Code Reusability)、代码复用(Code Resue)和 DRY 原则。

代码复用表示一种行为:在开发新功能的时候,尽量复用已经存在的代码。代码的可复用性表示一段代码可被复用的特性或能力:在编写代码的时候,让代码尽量可复用。DRY 原则是一条原则:不要写重复的代码。从定义描述上,它们好像有点类似,但深究起来,三者的区别还是蛮大的。首先,“不重复”并不代表“可复用”。其次,“复用”和“可复用性”关注角度不同。

提高代码可复用性的一些方法,有以下 7 点:

  • 减少代码耦合
  • 满足单一职责原则
  • 模块化
  • 业务与非业务逻辑分离
  • 通用代码下沉
  • 继承、多态、抽象、封装
  • 应用模板等设计模式

除了上面讲到的这些方法之外,复用意识也非常重要。在设计每个模块、类、函数的时候,要像设计一个外部 API 一样去思考它的复用性。

8. 迪米特法则(LOD)

8.1 高内聚、松耦合

“高内聚、松耦合”是一个非常重要的设计思想,能够有效提高代码的可读性和可维护性,缩小功能改动导致的代码改动范围。“高内聚”用来指导类本身的设计,“松耦合”用来指导类与类之间依赖关系的设计。不过,这两者并非完全独立不相干。高内聚有助于松耦合,松耦合又需要高内聚的支持。

所谓高内聚,就是指相近的功能应该放到同一个类中,不相近的功能不要放到同一个类中。相近的功能往往会被同时修改,放到同一个类中,修改会比较集中。

所谓松耦合是说,在代码中,类与类之间的依赖关系简单清晰。即使两个类有依赖关系,一个类的代码改动也不会或者很少导致依赖类的代码改动。

8.2 迪米特法则

英文翻译是:Law of Demeter,缩写是 LOD。也叫作最小知识原则,英文翻译为:The Least Knowledge Principle。

每个模块(unit)只应该了解那些与它关系密切的模块(units: only units “closely” related to the current unit)的有限知识(knowledge)。或者说,每个模块只和自己的朋友“说话”(talk),不和陌生人“说话”(talk)。

不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口(也就是定义中的“有限知识”)。基于最小接口而非最大实现编程。

更多推荐

(3)设计模式

本文发布于:2024-02-11 15:24:35,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1681732.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:模式

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!