当前位置: 首页 > news >正文

把设计模式用起来(3)用不好的原因之时机不对


上一篇:《把设计模式用起来(3)——用不好的原因 之 实践不足》icon-default.png?t=O83Ahttps://blog.csdn.net/nanyu/article/details/141939342

本篇继续讲设计模式用不好的常见原因,这是第二个:使用设计模式的时机不对。

二、时机不对

这里说的时机并不是单纯指软件研发周期中的时间阶段,而是指条件;更具体一点,指程序员应该满足什么条件,才是使用设计模式的好时机。

第一个条件前面说过了,得有问题。第二个条件,程序员需具备分析问题的能力。毕竟,只有通过分析才能从问题得到模式,如图:

图:  从问题到模式

这张图中最重要的是分析,最不重要的是模式。你甚至可以认为,一个没学过模式的程序员面对某个具体问题,只要分析得当,往往也能应用正确的模式,只是他自己不知道自己组织代码方式,被称作某某模式。

那么,我们要如何获得更强的分析能力呢?其一,对问题所属的业务逻辑越熟悉,对问题就越了解;其二,既然模式是此过程的结果,那么,熟悉模式也有利于我们强化分析问题的能力。由此,我们可以画一张更复杂一点的图:

图:  问题-分析-模式

尽管熟悉模式倒过来有助于理解问题,但程序员的更多努力仍需用在正向路径:熟悉业务→理解问题→分析问题→找到并应用正确的设计模式。

在理解问题的基础上,思考是否需要以及如何使用设计模式,这个结论听起来是如此的自然而然,几乎称得上是一条“公理”,但确实有很多程序员,特别是正在学习设计模式或自诩擅长设计模式的程序员,容易违背,犯“先有模式,然后到处找问题”的错误。

有同学说,“老师,虽然我心里确实装满设计模式,快溢出来的那种,但我并不会犯拿模式硬套问题的低级错误;相反,我总是在看到某个问题和某个模式匹配度很高的情况后,才会放心地套用设计模式。”

“只是放心吗?难道没有开心?”

“当然也开心啊!每当用对一次设计模式,我的内心充满成就感。”

这正是罪之所在:对设计模式的使用,有迫切的期待。这是贪欲,这是心魔,是引发更多软件研发问题的万恶之源。这种心态很容易让我们提倡的“工作逼迫你使用设计模式”,变成“你逼迫工作使用设计模式”。

说这么多,是在表达一个观点:使用设计模式应宁缺勿滥,因为,一段用错设计模式的代码往往比未使用模式的代码,更令后来人头痛。

每一个设计模式,都是在表达特定意图(Intent)的一种组织代码的方式。一段代码用上一个设计模式,意味着这段代码至少多出了两个明显的属性:一是它的意图,二是它的代码结构。一段什么设计模式都没用上代码,它就是一段代码;而一段代码用上了设计模式,代码中的设计模式就像人群有个显眼包,它会一直叫嚷:“看我,看我!我的设计意图是……我的实现结构是……”

那么,阅读者接收到这两个信息,是好事还是坏事?这就得看设计模式用对与否。幸福的代码都是相似的,不幸的代码却各有各的不幸,见表:

功能满足

(业务)

模式选择

(意图)

模式实现

(结构)

不幸的效果

表达了错误的意图且该意图未正确实现,程序不对劲

表达并实现了一个错误的意图,程序不对劲

意图正确,但该意图实现有错,程序不对劲

表达了错误的意图,且意图实现有误,但程序能跑对

用一个错误的模式实现了程序所需的功能

无论模式选对选错,也无论功能满足与否,表中几种应用情况中的设计模式,都会给代码阅读者带来更多干扰甚至误导,毕竟它们会说话。

这里特别讲讲表中最后一类不幸:“用一个错误的模式实现了程序所需功能”。这里最常见的“错误”,就是用了一个并不需要的模式。比如,使用策略模式,但直至系统下线也没用上第二个策略。再如,实现了一个无比强大的解释器,却只用它顺序执行指令。

丁小明严肃地从座位站起来:“老师,您应知道‘防御性编程’?程序员总是预设程序存在问题且将持续需要修改。所以,尽管在刚开始做设计时我们无法预测是否有第二个策略,但我们应该预设它有。至于解释器的例子,确实有点过度设计之嫌,但原则上,假定未来会有复杂的流程脚本需要执行,不对吗?”

这是很多同学都会有的疑惑:防御性编程是对的,可是切忌过度设计也是对的,日常编程做设计,能不能有一个简单明了且有效的方法,能迅速将我们从“加上!”和“不要!”,甚至是“砍掉!”的纠结中拉出来?

具体到何时使用设计模式这个问题,新的疑惑是:假设我还没有完全掌握业务需求就被迫写代码,那这些代码未来一定会变,那么,我现在是不是应该预防性地多加使用设计模式?毕竟,设计模式生来就是为了应对变化。让我们画张图来更清楚的表达:

因为会有变化,所以就马上用模式?

这种想法是错的,它把工作理想化了,并且弄错了工作首要目标。编程工作首先需保证正确实现当下的功能,然后才去考虑如何更好地就应对未来的变化。

为什么是这样先后次序?有两个理由,第一个略显功利:先快点做对,再慢慢做好,这样比较不会挨训,也可以反过来说,你上来就花大力气让代码能应对未来的65536种变化,大概率也得不到领导夸奖。第二个理由很客观:后者依赖前者,即,通常,正是在从不对,到不那么对,到最终做对的过程中,才能对眼下的功能、未来的变化、应有的设计这三者都产生深刻的理解。

丁小明再次发问:“有些功能未来会发生的变化,在程序员一行代码未写时,就可以猜出十之八群,这种情况下,程序员直接用上设计模式,也不合理吗?”这种理想状态当然存在。诸如:

  • 项目前期工作非常棒,需求清晰,设计详细,详细到此处该用什么模式都写出来了;
  • 你对此类问题有深刻理解,或有可靠的经验,此类功能后续的变化路径,你看得清楚、也看得长远;
  • 针对眼前这类问题,业界有教科书般的解决方法。

更多的时候,程序员处在水深火热中:

  • 甲方对现有的功能都说不清楚,遑论未来的变化;
  • 甲方基于防御性心理,罗列了各种各样的,未来可能的变化;
  • 甲方没怎么提,我方(领导、产品经理、程序员自己)设想了一大堆;
  • 甲方没怎么提,我方也没怎么想,但因为业务领域问题,我方并未正确理解甲方的真实需求;
  • 甲方提了一些,我方想了一些,并且我方正确地理解了甲方,只是不管甲方说还是我方想的,都有不对的地方;
  • ……

如果某一块的功能需求还比较模糊,作为乙方,千万不要自信可以通过一个“强大的设计”以做到“以不变应万变”,从而“立于不败之地”。此时的正确做法,应是直接的、快速地把当前所理解的功能做出来,并借助它尽快确定相关需求。

对问题的理解是一切设计的基础,不过,实现设计的过程,能极大帮助程序员全面地,深入地理解问题。多数时候,甲方能告诉你需求,但不能帮你做需求分析,而程序员因为跨领域的原因,所做的需求分析仍然有不少模糊与暧昧。比如,功能F1客观上需要的输入是I1、I2、I3,但程序员有可能在开始时认为是I1、I3和I4。有很多方法有助发现并纠正这个错误认知,其中一种非常高效的方法,就是编写代码尝试实现,在实现的过程,即可验证、强化、纠偏、补充程序员对业务系统的理解,包括厘清对象职责和理清对象关系,这二者是面向对象和用对设计模式的重要基础。

这是本小节的结论:除非你对问题理解到位透彻,否则不要一开始写代码就想着使用设计模式,在功能写对之前,更不要为了该功能“未来可能的变化”去套用设计模式。

这个结论只说了不要,那什么时候可以要呢?两点:

  1. 如果你对问题和业务所在领域非常有经验,那么,可以直接上设计模式以应对未来的变化;
  2. 否则,请在变化发生一次、两次、甚至三次的时候,再开始考虑使用设计模式重构代码。

台下有同学一脸落莫,问:“老师,我赞同第二点,但我们的领导不愿意给我重构所需的资源,比如时间,怎么办?”。这是回到上一小节“实践不足”中的情况B了。你可以屈服,从此不去想设计模式的好;也可以造反,说服领导,或想办法让自己当上领导;还也可以跳槽。无论如何,我都不建议一个程序员在这种恶劣的工作环境下,一边要抓紧完成业务功能,一边要努力用对设计模式。注意,当我说这话时,我站边技术主管。本来,主管只需检查你功能做对做错,现在,他需要同时检查功能和模式,其中后者还有需区分选择和实现上的错误,诸如:(a) 功能做错,模式选错,模式写错;(b) 功能做错,模式选对,模式写错; (c) 功能做对,模式选错,模式写对……(参见表2)。

猜,技术主管在这种场景下,最喜欢说的是哪一句话?答:“你这是要把错误雕成一朵花吗?”

来听一个源于真实案例改编的故事吧。主人公本应是丁小明,为节省篇幅,我们用“你”来代替。

假设问题为Q,正确答案是A;而因为理解有误,你以为需要实现的业务逻辑是B,并且你迅速想到B未来有可能发展成B1、B2、B3。想到这里,你一边啜吸奶茶发出嗞嗞嗞的声音;一边在脑海中构建原始思路C。为了更好地表达思路,也为了更好地应对B1、B2、B3,你果断选用设计模式D,D的意图是I。不过,由于你还是设计模式新人,所以你真实想表达的意图其实是J,而你写的模式很像D但又不是D,称作“D' ”。测试一把,确实能实现B,你充满成就感地提交了代码E。

技术主管开始审查E,他迅速嗅到D的味道;会心一笑,品一口咖啡,感觉自己像羽扇纶巾的诸葛亮,准备欣赏一场漂亮的战役,看看自己的员工如何通过设计模式优雅地干掉Q。10分钟过去,20分钟过去,30分钟过去……经验老到的主管,终于发现D` 和 D 只是长得像而已。

主管放下代码,翻出上次设计模式内训的文档,发现当时你写的答案那叫一个似是而非。他叹一口气,陷入自责。

1个小时过去,主管猛然领悟,你的真实意图是J。

2个小时过去,主管醍醐灌顶:“这家伙想要实现的业务逻辑是B,不是A!”,不过,纵使如此,也不应该用D啊?看一眼时间是夜里11点30分,还早;他拨通了你的电话。

夜深人静,主管的身边人呼吸均匀,偶尔吐一两句梦话。主管轻悄悄地换个姿式,继续听话里的你兴奋地谈着:“B1、B2、B3变化,可能还是保守了,我有一种预感,三个月以后,随着用户量的剧增,B99,B100,B101也是有可能发生的……”

这样的破事来个三四次,故事中主人公的工作类型就会发生变化,参见上一小节“实践不足”的情况D、情况C、情况A。

相关文章:

把设计模式用起来(3)用不好的原因之时机不对

上一篇:《把设计模式用起来(3)——用不好的原因 之 实践不足》https://blog.csdn.net/nanyu/article/details/141939342 本篇继续讲设计模式用不好的常见原因,这是第二个:使用设计模式的时机不对。 二、时机不对 这里…...

【机器学习随笔】基于kmeans的车牌类型分类注意点

kmeans是无监督的聚类算法,可用于数据的分类。本文尝试用kmeans对车牌类型进行分类,记录使用过程中的注意点。 kmeans使用过程中涉及两个大部分,模型与分析。模型部分包括训练模型和使用模型,分析部分主要为可视化分析。两部分的主…...

matlab处理函数3

1. 直方图均衡化的 Matlab 实现 1.1 imhist 函数 功能:计算和显示数字数字图像的色彩直方图 格式:imhist(I,n) imhist(X,map) 说明:imhist(I,n) 其中,n 为指定的灰度级数目,缺省值为256;imhist(X…...

跨系统环境下LabVIEW程序稳定运行

在LabVIEW开发中,不同电脑的配置和操作系统(如Win11与Win7)可能对程序的稳定运行产生影响。为了确保程序在不同平台上都能正常且稳定运行,需要从兼容性、驱动、以及性能优化等多个方面入手。本文将详细介绍如何在不同系统环境下&a…...

开源项目低代码表单FormCreate中通过接口加载远程数据选项

在开源项目低代码表单 FormCreate 中,fetch 属性提供了强大的功能,允许从远程 API 加载数据并将其应用到表单组件中。通过灵活的配置,fetch 可以在多种场景下发挥作用,从简单的选项加载到复杂的动态数据处理。 源码地址: Github …...

k8s的搭建

一、安装环境 准备三台主机: 192.168.1.66 k8s-master 192.168.1.77 k8s-node01 192.168.1.88 k8s-node02 网段: Pod ⽹段 172.16.0.0/16 Service ⽹段 10.96.0.0/16 注:宿主机⽹段、Pod…...

人工智能与机器学习原理精解【19】

文章目录 马尔科夫链概述定义与性质分类应用领域收敛性马尔科夫链蒙特卡洛方法 马尔科夫链原理详解一、定义二、特性三、数学描述四、类型五、应用六、示例定义性质转移概率矩阵应用举例结论 马尔科夫链在语音识别和语音合成中的应用一、马尔科夫链在语音识别中的应用1. 基本概…...

DingoDB:多模态向量数据库的实践与应用

DingoDB:多模态向量数据库的实践与应用 1. 引言 在当今数据驱动的时代,高效处理和分析大规模、多样化的数据变得至关重要。DingoDB作为一个分布式多模态向量数据库,为我们提供了一个强大的解决方案。本文将深入探讨DingoDB的特性、安装过程…...

03.01、三合一

03.01、[简单] 三合一 1、题目描述 三合一。描述如何只用一个数组来实现三个栈。 你应该实现push(stackNum, value)、pop(stackNum)、isEmpty(stackNum)、peek(stackNum)方法。stackNum表示栈下标,value表示压入的值。 构造函数会传入一个stackSize参数&#xf…...

github上clone代码过程

从 GitHub 上拉取代码的过程非常简单,一般通过 git clone 命令来完成。以下是详细步骤: 下载git工具 要下载并安装 Git,你可以根据你的操作系统来选择相应的步骤。以下是如何在不同操作系统上安装 Git 的详细说明: 1. 在 Windo…...

ChatGLM3模型搭建教程

一、介绍 ChatGLM3 是智谱 AI 和清华大学 KEG 实验室联合发布的对话预训练模型。ChatGLM3-6B 是 ChatGLM3 系列中的开源模型,在保留了前两代模型对话流畅、部署门槛低等众多优秀特性的基础上,ChatGLM3-6B 引入了如下特性: 更强大的基础模型…...

多层建筑能源参数化模型和城市冠层模型的区别

多层建筑能源参数化(Multi-layer Building Energy Parameterization, BEP)模型和城市冠层模型(Urban Canopy Model, UCM)都是用于模拟城市环境中能量交换和微气候的数值模型,但它们的侧重点和应用场景有所不同。以下是…...

27. Redis并发问题

1. 前言 对于一个在线运行的系统,如果需要修改数据库已有数据,需要先读取旧数据,再写入新数据。因为读数据和写数据不是原子操作,所以在高并发的场景下,关注的数据可能会修改失败,需要使用锁控制。 2. 分布式场景 2.1 分布式锁场景 面试官提问: 为什么要使用分布式锁?…...

JVM四种垃圾回收算法以及G1垃圾回收器(面试)

JVM 垃圾回收算法 标记清除算法:标记清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。 在标记阶段通过根节点,标记所有从根节点开始的对象。然后,在清除阶段,清除所有未被标记的对象 适用场合: 存活对…...

Python 数学建模——Vikor 多标准决策方法

文章目录 前言原理步骤代码实例 前言 Vikor 归根到底其实属于一种综合评价方法。说到综合评价方法,TOPSIS(结合熵权法使用)、灰色关联度分析、秩和比法等方法你应该耳熟能详。Vikor 未必比这些方法更出色,但是可以拓展我们的视野。…...

计算机网络八股总结

这里写目录标题 网络模型划分(五层和七层)及每一层的功能五层网络模型七层网络模型(OSI模型) 三次握手和四次挥手具体过程及原因三次握手四次挥手 TCP/IP协议组成UDP协议与TCP/IP协议的区别Http协议相关知识网络地址,子…...

AMD CMD UMD CommonJs ESM 的历史和区别

这几个东西都是用于定义模块规范的。有些资料会提及到这些概念,不理清楚非常容易困惑。 ESM(ES Module) 这个实际上我们是最熟悉的,就是ES6的模块功能。出的最晚,因为是官方出品,所以大势所趋&#xff0c…...

人工智能数据基础之微积分入门-学习篇

目录 导数概念常见导数和激活导数python代码绘制激活函数微分概念和法则、积分概念微积分切线切面代码生成案例链式求导法则反向传播算法(重要) 一、概念 二、常见导数及激活导数 常见激活函数及其导数公式: 在神经网络中,激活函数用于引入非线性因素&…...

【PSINS】ZUPT代码解析(PSINS_SINS_ZUPT)|MATLAB

这篇文章写关于PSINS_SINS_ZUPT的相关解析。【值得注意的是】:例程里面给的这个m文件的代码,并没有使用ZUPT的相关技术,只是一个速度观测的EKF 简述程序作用 主要作用是进行基于零速更新(ZUPT)的惯性导航系统(INS)仿真和滤波 什么是ZUPT ZUPT是Zero Velocity Update(…...

多态(上)【C++】

文章目录 多态的概念多态的实现多态产生的条件什么是虚函数?虚函数的重写和协变重写协变 析构函数的重写为什么有必要要让析构函数构成重写? 多态的概念 C中的多态是面向对象编程(OOP)的一个核心特性,指的是同一个接口…...

循环冗余码校验CRC码 算法步骤+详细实例计算

通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)&#xff0…...

Qt Widget类解析与代码注释

#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...

Golang dig框架与GraphQL的完美结合

将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...

vue3 字体颜色设置的多种方式

在Vue 3中设置字体颜色可以通过多种方式实现&#xff0c;这取决于你是想在组件内部直接设置&#xff0c;还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法&#xff1a; 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心

当仓库学会“思考”&#xff0c;物流的终极形态正在诞生 想象这样的场景&#xff1a; 凌晨3点&#xff0c;某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径&#xff1b;AI视觉系统在0.1秒内扫描包裹信息&#xff1b;数字孪生平台正模拟次日峰值流量压力…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包

文章目录 现象&#xff1a;mysql已经安装&#xff0c;但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时&#xff0c;可能是因为以下几个原因&#xff1a;1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

.Net Framework 4/C# 关键字(非常用,持续更新...)

一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...

Python ROS2【机器人中间件框架】 简介

销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

【Go语言基础【12】】指针:声明、取地址、解引用

文章目录 零、概述&#xff1a;指针 vs. 引用&#xff08;类比其他语言&#xff09;一、指针基础概念二、指针声明与初始化三、指针操作符1. &&#xff1a;取地址&#xff08;拿到内存地址&#xff09;2. *&#xff1a;解引用&#xff08;拿到值&#xff09; 四、空指针&am…...

纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join

纯 Java 项目&#xff08;非 SpringBoot&#xff09;集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...