我的设计模式之旅、00 前置知识

本内容主要来自《深入设计模式》亚历山大·什韦茨(Alexander Shvets)

不足之处

《大话设计模式》主要讲故事,对GOF的理论不是特别深入,部分概念并没有说清楚,比如里氏替换原则的几点要求,三大类设计模式各个概念等等,为了补充相关知识我阅读了《深入设计模式》,于是有了这篇前置文章。方便阅读《大话设计模式》的入门程序员了解更多基础知识。

前置知识

基础知识

面向对象程序设计

基本理念是将数据块及其数据相关的行为封装成为特殊的、名为对象的实体。

UML图相关补充

image-20220914222901393

image-20220914222936418

这种由各种类组成的金字塔就是层次结构

image-20220915162905617

UML 图不会展示所有依赖——它们在真实代码中的数量太多了。为了不让依赖关系破坏 UML 图,你必须对其进行精心选择,仅展示那些对于沟通你的想法来说重要的依赖关系。

成员变量和成员方法

成员变量和方法可以统称为类的成员。 存储在对象成员变量中的数据通常被称为状态,对象中的所有方法则定义了其行为

抽象

抽象是一种反映真实世界对象或现象中特定内容的模型,它能高精度地反映所有与特定内容相关的详细信息,同时忽略其他内容。

接口

接口是对象的公有部分,能够同其他对象进行交互。接口仅关心对象行为。

封装

封装是指一个对象对其他对象隐藏其部分状态和行为,而仅向程序其他部分暴露有限的接口的能力。

继承

使用继承后,子类将拥有与其父类相同的接口。如果父类中声明了某个方法,那么你将无法在子类中隐藏该方法。你还必须实现所有的抽象方法,即使它们对于你的子类而言没有意义。

多态

多态是指程序能够检测对象所属的实际类,并在当前上下文不知道其真实类型的情况下调用其实现的能力。还可将多态看作是一个对象“假扮”为其他东西。

对象之间关系

依赖

如果修改一个类的定义可能会造成另一个类的变化,那么这两个类之 间就存在依赖关系。

当你在代码中使用具体类的名称时,通常意味着存在依赖关系。例如在指定方法签名类型时,或是通过调用构造函数对对象进行初始化时等。

通过让代码依赖接口或抽象类(而不是具体类),你可以降低其依赖程度。

关联

关联是一个对象使用另一对象或与另一对象进行交互的关系。一个对象总是拥有访问与其交互的对象的权限,而简单的依赖关系并不会在对象间建立永久性的联系。

一般来说,你可以使用关联关系来表示类似于类成员变量的东西。但是它并非一定是成员变量。

聚合

聚合是一种特殊类型的关联,用于表示多个对象之间的“一对多”、“多对多”或“整体对部分”的关系。

一个对象“拥有”一组其他对象,并扮演着容器或集合的角色。组件可以独立于容器存在,也可以同时连接多个容器。

依赖关联聚合组合实现继承

image-20220915163408789

设计模式简介

算法与模式

算法总是明确定义达成特定目标所需的一系列步骤,而模式则是对解决方案的更高层次描述。

算法更像是菜谱:提供达成目标的明确步骤。而模式更像是蓝图:你可以看到最终的结果和模式的功能,但需要自己确定实现步骤。

最基础的、底层的模式通常被称为惯用技巧。最通用的、高层的模式是架构模式。

设计模式三大类

  • 创建型模式提供创建对象的机制,增加已有代码的灵活性和 可复用性。
  • 结构型模式介绍如何将对象和类组装成较大的结构,并同时保持结构的灵活和高效。
  • 行为模式负责对象间的高效沟通和职责委派。

设计模式与框架

设计模式比框架更小且更抽象。 它们实际上是对一组类的关系及 其互动方式的描述。当你从类转向模式,并最终到达框架的 过程中,复用程度会不断增加。模式提供的复用方式要比框架的风险小。

设计原则

封装变化内容

找到程序中的变化内容并将其与不变的内容区分开。将变更造成的影响最小化。将程序的变化部分放入独立的模块中,保护其他代码不受负面影响。

方法层面的封装,你可以将算法逻辑抽取到一个单独的方法中,并对原始方法隐藏该逻辑。

类成员的封装,你可能会在一个以前完成简单工作的方法中添加越来越多的职责。新增行为通常还会带来助手成员变量和方法,最终使得包含接纳它们的类的主要职责变得模糊。将所有这些内容抽取到一个新类中会让程序更加清晰和简洁。

面向接口开发, 不是面向实现

面向接口进行开发,而不是面向实现;依赖于抽象类 型,而不是具体类。

  1. 确定一个对象对另一对象的确切需求:它需执行哪些方法?
  2. 在一个新的接口或抽象类中描述这些方法。
  3. 让被依赖的类实现该接口。
  4. 现在让有需求的类依赖于这个接口, 而不依赖于具体的类。 你仍可与原始类中的对象进行互动,但现在其连接将会灵活 得多。

组合优于继承

继承问题清单

  • 子类不能减少超类的接口。
  • 在重写方法时,你需要确保新行为与其基类中的版本兼容。
  • 继承打破了超类的封装,因为子类拥有访问父类内部详细内容的权限。
  • 子类与超类紧密耦合。
  • 通过继承复用代码可能导致平行继承体系的产生。多个维度导致类层次结构膨胀!

image-20220915164236341

用组合将不同“维度”的功能抽取到各自的类层次结构中。

设计原则-SOLID

从实用的角度来考量,不要把这里的每句话当作放之四海皆准的教条。

单一职责原则

修改一个类的原因只能有一个。

如果你开始感觉在同时关注程序特定方面的内容时有些困难的话,请回忆单一职责原则并考虑现在是否应将某些类分割为几个部分。

开放封闭原则

对于扩展,类应该是“开放”的;对于修改,类则应 是“封闭”的。

里氏替换原则

当你扩展一个类时, 记住你应该要能在不修改客户端代码的情况下将子类的对象作为父类对象进行传递。

具体要求

  • 子类方法的参数类型必须与其超类的参数类型相匹配或更加抽象。
  • 子类方法的返回值类型必须与超类方法的返回值类型或是其子类别相匹配。
  • 子类中的方法不应抛出基础方法预期之外的异常类型。
  • 子类不应该加强其前置条件。
  • 子类不能削弱其后置条件。
  • 超类的不变量必须保留。
  • 子类不能修改超类中私有成员变量的值。

image-20220915164610576

你可以通过重新设计类层次结构来解决这个问题:一个子类必须扩展其超类的行为,因此只读文档变成了层次结构中的基类。可写文件现在变成了子类,对基类进行扩展并添加了保存行为。

接口隔离原则

客户端不应被强迫依赖于其不使用的方法。

是将接口拆分为多个部分。能够实现原始接口的类现在只需改为实现多个精细的接口即可。其他类则可仅实现对自己有意义的接口。

创建的接口越多,代码就越复杂。因此要保持平衡。

低层次类、高层次类

低层次的类实现基础操作(例如磁盘操作、传输网络数据和 连接数据库等)。

高层次类包含复杂业务逻辑以指导低层次类执行特定操作。

实际开发过程中,由于低层次的东西还没有实现或不确定,你甚至无法确定高层次类能实现哪些功能。

依赖倒置原则

高层次的类不应该依赖于低层次的类。 两者都应该依 赖于抽象接口。抽象接口不应依赖于具体实现。具体实现应该依赖于抽象接口。

如何做到依赖倒置

  1. 使用业务术语来对高层次类依赖的低层次操作接口进行描述。业务逻辑应该调用名为 openReport(file) 的方法, 而不是openFile(x) 、readBytes(n) 和 closeFile(x) 等一系列方法。
  2. 现在你可基于这些接口创建高层次类,而不是基于低层次的具体类。
  3. 一旦低层次的类实现了这些接口,它们将依赖于业务逻辑层, 从而倒置了原始的依赖关系。

依赖倒置原则通常和开闭原则共同发挥作用:你无需修改已有类就能用不同的业务逻辑类扩展低层次的类。

参考资料

  • 《Go语言核心编程》李文塔
  • 《Go语言高级编程》柴树彬、曹春辉
  • 《大话设计模式》程杰
  • 《深入设计模式》亚历山大·什韦茨
  • 菜鸟教程
posted @ 2022-09-15 16:56  小能日记  阅读(23)  评论(0编辑  收藏  举报