软件设计模式(一)

2023-11-06

本章学习主要参照Datawhale开源学习及《大话设计模式》

本项目结合《大话设计模式》这本书,总结了各种设计模式的基本概念知识点和适用场景。先通过书中的案例,介绍了23种设计模式及其相关的代码示例,项目中有多种语言代码示例,本文主要采用Python语言进行说明。

第1章 前言

1.1 什么是设计模式

设计模式是软件开发过程中一些常见问题的典型解决方案。我们可以将其理解为软件开发的套路

它不同于算法,并不能告诉我们面对某个问题时应该怎么一步步操作,它是更加抽象的软件设计架构蓝图,我们可以看到模式和设计,但并不关心具体实现。

小到一个模块大到一整套软件系统,都可以应用设计模式。可以多个模式同时使用,也可以部分模式只使用了其中一部分。不同的模式适用于不同的功能和设计,与此同时,同样的功能和设计在不同阶段或背景下也可能需要不同的模式。因此,模式没有好坏,只有适不适合。

1.2 为什么需要设计模式

设计模式是经过多年实践验证有效的解决思路,无论是新开发软件还是进行重构,它都可以让系统更加稳固代码更加清晰

设计模式是一种可用于工程师之间沟通的高效语言,当我们在讨论和分析问题时,简单几个字就可以让彼此明白心意。

1.3 二十三种设计模式

23 个设计模式,一般又被分为三种类型:

  • 创建型模式(5 个):与对象的创建有关。
    工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
  • 结构型模式(7 个):与对象的组装有关。
    适配器模式、装饰模式、外观模式、桥接模式、代理模式、组合模式、享元模式
  • 模式(11 个):与对象之间的沟通协调有关。
    观察者模式、命令模式、状态模式、职责链模式、模板方法模式、策略模式、解释器模式、中介者模式、访问者模式、备忘录模式、迭代器模式

面向对象设计的基本原则包括:

  • (封装)针对接口编程,而不是针对实现编程。(依赖倒置原则,迪米特原则)
  • (继承)优先使用对象组合,而不是类继承。(里氏替换原则)
  • (多态)封装变化,将不变的与变化的内容分开。(单一职责原则,开闭原则)

第2章 设计原则

1.1 单一职责原则

易修改:一个类只有一个职责

单一职责原则,The Single Responsibility Principle,简称 SRP,是指就一个类而言,应该仅有一个更改它的原因。也即这个类只有一个职责。

  • 使用动机

若一个类有一个以上的职责,则当一个职责发生变化时,可能会影响其他职责,从而影响代码的维护。

  • 如何使用

核心在于职责的分解。需要将相同的职责放到一起,不同的职责分开到不同的类的实现中去。

  • 使用原则

每一个类实现的职责有清晰明确的定义。一个类的修改只对自身有影响,对其他类没有影响。

  • 使用示例

以一个俄罗斯方块游戏为例。

最直观的想法是,用一个计时器控制动画,每一个计时器编写绘制和擦除方块的逻辑,模拟下落时方块形状的变化,再做一个堆积和消层的判断,最后提供键盘控制逻辑。

假设我们一开始做的是 Web 游戏,游戏效果不错后需要增加 3D 版、手机版等。此时,相对变化的只有方块的样式,但由于我们的代码都在一块,导致其他不变的逻辑没法复用。这就是职责过多的情况,接下来考虑如何将职责分解。

最简单的方法就是将变化的和相对不变的部分分开,也就是将游戏的逻辑(不变的部分)和界面的展示(易变的部分)分开。具体来说,可以将游戏区域设计为二维数组,通过坐标来表示每个方块,实际显示出的方块就是坐标值为 1 的方块。这样,就可以通过值为 1 的坐标的变化模拟出方块的堆积和变换。也就是说,游戏的操作和判断逻辑(如变换、移动、堆积、消层等)其实就是坐标值的变化。界面的展示只是根据数组数据进行绘制。

1.2 开闭原则

易扩展:软件实体可扩展但不可修改

开闭原则,The Open-Closed Principle,简称 OCP,是指软件实体(类、模块、函数等)应该可以扩展,但是不可以修改。即对于扩展是开放的,对于更改是封闭的。

  • 使用动机

面对需求改变可以保持相对稳定,使得系统可以在第一个版本以后不断推出新的版本。

  • 如何使用

在开发工作展开前预测可能的变化,或展开不久后知道可能发生的变化,或当实际需要发生时带来的变化。创建抽象,隔离以后发生的同类变化。

  • 使用原则

仅对程序中呈现出频繁变化的部分做出抽象,不要刻意对每个部分进行抽象

  • 使用示例

以一个加法器为例。

当我们在接到这个需求时,就可以很容易地想到以后可能需要减法、乘法、除法等等运算。所以,我们在开发的时候就可以先把「加法」抽象成一个类,这样,以后需要其他运算时,只要增加一个类即可。

实际做的时候我们可能会发现用户输入的可能是个表达式,不一定是两个数字,有可能是三个数字,还有可能有括号。这时候我们可能会写一个四则混合运算解析器类。以后如果需要解析其他表达式(如复数),则只需增加对应的类。

好不容易做完了,突然又来了新需求,需要做一个字符串的加法(即拼接),我们假设 API 不变。此时,我们可能需要设计一个输入表达式判断器类,用于判断是数字还是字符串的运算。我们还需要对原来的代码进行一些调整,以便 API 进来的输入能够首先通过判断器。

1.3 依赖倒置原则

易拔插:要针对接口编程,不要针对实现编程

依赖倒置原则,Dependence Inversion Principle,简称 DIP,是指程要针对接口编程,不要针对实现编程。

  • 使用动机

面对不同的具体实现做到易拔插,松耦合。

  • 如何使用

使用接口或抽象类的目的是制定好规范,不涉及任何具体的操作,把展现细节的任务交给实现类去完成。让程序中的所有依赖关系都终止于抽象类或接口。

  • 使用原则

高层模块不应该依赖低层模块,两个都应该依赖抽象。抽象不应该依赖细节,细节应该依赖抽象。

  • 使用示例

以计算机为例。

刚开始的计算机是自成体系的,虽然都是采用同样的设计架构和结构,但组件之间的连接方式不同。如果用 A 公司的电脑,硬盘坏了后只能用 A 公司提供的硬盘。这是一种紧耦合的表现,每个组件将其内部实现暴露给外部对接。

后来几家大公司统一了标准,约定好组件之间连接的标准,标准后面具体怎么做,由相应公司自己负责。这样的结果是,我们既可以使用 A 公司的硬盘,也可以使用 B 公司的硬盘。不光如此,不同大小(如 500G 和 200G)、不同结构(如固态硬盘和机械硬盘)的硬盘也可以互换。真正实现了可拔插、易拔插。

除了硬盘,其他如 CPU、内存、外设设备等各种设备组件也都实现了标准化接口。所有的对接都发生在接口层面,不需要关心具体的实现细节。

1.4 里氏替换原则

易重用:父类一定适用于其子类

里氏替换原则,Liskov Substituion Principle,简称 LSP,一个软件实体如果使用的是一个父类的话,一定适用于其子类,而且它察觉不出父类和子类的区别。也就是说,在软件里面,把父类都替换成它的子类,程序的行为没有变化。

  • 使用动机

父类能够真正复用(继承),子类也能够在父类的基础上增加新的行为。

  • 如何使用

父类一般使用抽象类或接口。抽象类定义公共对象和状态;接口定义公共行为。子类通过继承父类和接口进行扩展。

  • 使用原则

子类方法的参数类型必须与父类相匹配或更抽象。子类的返回值类型必须与父类或其子类相匹配。子类方法的异常必须与父类能抛出的异常(或其子类)相匹配。子类不应该加强参数条件限制。子类不能修改父类的私有成员变量。

  • 使用示例

以企鹅和鸟为例。

假设鸟是父类,有下蛋和飞翔两个方法。企鹅作为鸟如果继承了父类,就会出现问题,因为企鹅虽然有翅膀但不会飞。所以,这样设计父类和子类是不合理的,它违反了上面提到的原则,企鹅作为子类无法替换父类。

合理的做法是将飞翔的行为抽象为接口,父类鸟描述状态和公共方法(比如吃),然后会飞的子类再去实现飞翔接口,不会飞的就不用管了。

1.5 迪米特原则

易维护:最小化类之间的通信,通信采用统一接口

迪米特法则,Law of Demeter,简称 LoD,也叫最小知识原则。是指如果两个类不必彼此互相通信,那么这两个类就不应当发生直接的相互作用;如果其中一个类需要调用另一个类的某一个方法,可以通过第三者转发这个调用。

  • 使用动机

强调类之间的松耦合。类之间的耦合越弱,越有利于复用和扩展。另外,一个处于弱耦合的类被修改,不会对有关系的类造成波及。

  • 如何使用

在类的结构设计上,每一个类都应当尽量降低成员的访问权限,不需要让别的类知道的字段或行为就不要公开。类之间不直接建立联系,通过中间类来中转。

  • 使用原则

减少公开方法和变量。每个类对其他类知道的越少越好。类不应该知道它所操作的对象的内部细节。

  • 使用示例

以跨部门办事为例。

假设我们电脑出问题了,第一反应是找运维部门的熟人帮忙看看,这等于直接操作了运维部门的内部。这样的好处几乎没有,但问题很多。也许有人会说都找到人直接解决问题了,不是很高效吗,其实并不是。首先,你找熟人时,他不一定有时间,也许手里还有更重要、更紧急的事情在忙;其次,他也许也不知道你的问题,或者说他不负责这一块,无能为力;还有,如果他人际关系不错,很多人出问题了都来找他,还会导致部门内工作不均衡;另外,他自己一直在解决问题,但是领导却并不知晓,即便知道也不能接受。总而言之,如果都去找熟人,最后效率和满意度都不会很高。

这时候最好的做法是提供一个运维部门的问题反馈入口,由这名同事将收集到的问题分门别类,统一分派给部门内相应的人员进行处理。这样不仅资源得到了均衡,而且还易于批量解决问题,领导查看也一目了然。即便有同事请假,那也是运维部门内部的事情,由内部自己解决,并不影响外部使用。

总之,设计原则讲究易修改、易扩展、易插拔、易重用、易维护的原则。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

软件设计模式(一) 的相关文章

随机推荐

  • html5中链接和分组标签

    本小白今天新学了一点html5标签的用法 内容简单 希望和大家分享一下 对html5感兴趣的小白可以去CSDN上找视频教程具体学习 http edu csdn net course detail 489 5454 auto start 1
  • 进程的休眠与唤醒(等待队列)

    1 进程休眠 1 进程有三种基本状态 就绪态 阻塞态 运行态 lt 1 gt 阻塞态 进程缺少除了CPU之外的某些资源 因此该进程不能被运行 被阻塞住了不能被CPU调度 lt 2 gt 就绪态 进程分配到了除CPU之外的所有资源 等待CPU
  • GLSL里自定义attribute变量

    在做法线贴图的时候需要传入顶点的切线数据 这个顶点属性值是不包含在GLSL默认提供里的 需要自己实现 于是学习了一下 其实非常简单 首先需要glGetAttribLocation来获取变量的索引值 然后调用glVertexAttrib对其进
  • 微电子专业

    作者IC修真院 今天来聊聊微电子高校 提到微电子专业 肯定不得不说9所首批示范性微电子学院 今天就来盘他们 类似于专业设置 培养方案 课程设置这类信息 在学校官网上都是可以直接搜索到的 就不和大家赘述了 这里主要就方向优势 科研能力和业内认
  • PHP网站设计思路

    本文是对 PHP and MySQL Web Development 第5版27章中项目的总结 1 分析所需功能 列出主要功能模块 登录 注册 忘记密码 重设密码 登出 书签浏览 书签增加 书签删除 书签推荐 确定模块之间的先后转移关系 2
  • 鼠标滚动事件 - onmousewheel

    1 Jquery MouseWheel jquery默认是不支持支持鼠标滚轮事件 mousewheel jquery MouseWheel下载 https github com jquery jquery mousewheel blob m
  • 先后离开谷歌、雅虎后,梅姐的 AI 创业公司,再获两千万美金融资

    内容提要 硅谷一家创业公司 Lumi Labs 完成了近 2000 万美元的融资 这家鲜为人知的初创公司 凭什么能够拿下如此巨额的融资 这也许与其背后的大 boss 有关 它的创始人正是前雅虎 CEO 玛丽莎 梅耶尔 Marissa May
  • @JsonIgnoreProperties 解决实体中引用其他实体问题

    解决办法 json转换成的实体类加注解 JsonIgnoreProperties ignoreUnknown true 注意这是类级别的注解 JsonIgnore注解用来忽略某些字段 可以用在Field或者Getter方法上 用在Sette
  • _MSC_VER详细介绍

    MSC VER是微软的预编译控制 MSC VER可以分解为 MS Microsoft的简写 C MSC就是Microsoft的C编译器 VER Version的简写 MSC VER的意思就是 Microsoft的C编译器的版本 微软不同时期
  • 深度学习入门 ---- 张量(Tensor)

    文章目录 张量 张量在深度学习领域的定义 张量的基本属性 使用PyTorch 安装PyTorch 查看安装版本 创建张量 常用函数 四种创建张量的方式和区别 四则运算 张量 张量在深度学习领域的定义 张量 tensor 是多维数组 目的是把
  • 计算机配置内存容量怎么调,如何设置电脑虚拟内存,电脑虚拟内存设置多少最合理?...

    电脑虚拟内存是为了缓解CPU运行的压力而产生的一种新技术也可以理解为把电脑硬盘分出来一部分空间当作内存来使用 今天小编就为大家讲解下如何设置电脑虚拟内存 电脑虚拟内存设置多少最合理 希望对大家有所帮助 1 点击电脑左下角的开始菜单 找到控制
  • JavaScript中defer的作用

    JavaScript中defer的作用 Javascript中defer的作用是文档加载完毕了再执行脚本 这样会避免找不到对象的问题 defer是脚本程序强大功能中的一个 无名英雄 它告诉浏览器Script段包含了无需立即执行的代码 并且
  • Java浅拷贝和深拷贝

    Java Android 基础知识梳理 11 浅拷贝 Vs 深拷贝 Java对象数组深拷贝 java List复制 浅拷贝与深拷贝
  • C# 索引器(Indexer)

    C 索引器 Indexer 转自 http www runoob com csharp csharp indexer html 索引器 Indexer 允许一个对象可以像数组一样被索引 当您为类定义一个索引器时 该类的行为就会像一个 虚拟数
  • ibm服务器阵列卡与型号,IBM阵列卡介绍和服务器对阵列卡的支持情况

    ZDNetChina服务器站 芯片 组件配置技巧 目前IBM的阵列卡从控制的硬盘来说可以分成三大类 控制SCSI硬盘的SCSI RAID控制器 ServeRaid ServeRaid II ServeRaid 3L ServeRaid 3H
  • 【Leetcode】257. 二叉树的所有路径

    题目描述 题解 能用String解决的最好不要走StringBuilder 递归时注意空结点 null 回退和叶子结点判定回退 执行用时 9 ms 在所有 Java 提交中击败了30 66 的用户 内存消耗 39 1 MB 在所有 Java
  • Python 中的range(),arange()函数

    Python 中的range arange 函数 arange函数用于创建等差数组 使用频率非常高 arange非常类似range函数 会python的人肯定经常用range函数 比如在for循环中 几乎都用到了range 下面我们通过ra
  • 概率论中的重要不等式(Markov/Chebyshev/Jensen)

    1 Schwarz 不等式 对于任意的随机变量 和 均有 证明 假设 否则 有 所以不等式成立 我们有 即 2 Markov不等式 设随机变量 只取非负值 则对任意的 证明 固定正数 定义一个随机变量 易知 总成立 从而有
  • 1071svm函数 r语言_R语言机器学习之核心包e1071 - 数据分析

    R语言有很多包可以做机器学习 Machine Learning 的任务 机器学习的任务主要有有监督的学习方式和无监督的学习方式 有监督学习 在正确结果指导下的学习方式 若是正确结果是定性的 属于分类问题 若正确结果是定量的 属于回归问题 无
  • 软件设计模式(一)

    本章学习主要参照Datawhale开源学习及 大话设计模式 本项目结合 大话设计模式 这本书 总结了各种设计模式的基本概念 知识点和适用场景 先通过书中的案例 介绍了23种设计模式及其相关的代码示例 项目中有多种语言代码示例 本文主要采用P