把设计模式用起来!(3)用不好模式?之时机不对


上一篇:《把设计模式用起来(2)——用不好?之实践不足》

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

二、时机不对

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

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

图:  从问题到模式

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

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

图:  问题-分析-模式

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

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

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

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

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

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

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

每一个设计模式,都是在表达特定意图(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。

下一篇 《把设计模式用起来!》(4)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/427300.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用rust自制操作系统内核

一、系统简介 本操作系统是一个使用rust语言实现,基于32位的x86CPU的分时操作系统。 项目地址(求star):GitHub - CaoGaorong/os-in-rust: 使用rust实现一个操作系统内核 详细文档:自制操作系统 语雀 1. 项目特性 …

数据库加密算法

功能简介 对数据库字段进行加密,如下图: 一、yml配置 注意: MD5_32 MD5_16 BASE64 AES SM2 SM3 SM4 需要 password(14位 ,26位, 32 位) 就行 非对称算法如:SM2,RSA, 需要配置 密码:password 公钥:publicKey 私钥:privateKey yml: # 数据加密 mybatis-encry…

【新手/小白教程】打开一个vue项目的前置准备,nvm安装指定版本node

目录 一、前言二、nvmnvm介绍nvm下载与安装1. 官网下载 nvm 包2. 安装 nvm-setup.exe3. 配置路径和下载镜像4. 检查nvm是否安装完成5. 错误情况 三、nodenode版本查看node命令 一、前言 在换新电脑的时候总是需要把所有东西重新安装配置,这篇用来记录一下打开一个v…

vmware中的ubuntu系统扩容分区

1.虚拟机关机 右击虚拟机/设置,进入虚拟机设置 3.启动虚拟机,进入命令行 4.fdisk -l查看要扩展的分区名 5.resize要扩容的分区 su root parted /dev/sda resizepart 3 100% fdisk -l resize2fs /dev/sda3 df -T完成 6.其他 进入磁盘管理 fdisk /d…

【深度智能】:迈向高级时代的人工智能全景指南

​ ​ 前几天偶然发现了一个超棒的人工智能学习网站,内容通俗易懂,讲解风趣幽默,简直让人欲罢不能。忍不住分享给大家,人工智能立刻跳转,开启你的AI学习之旅吧! 第一阶段:基础知识 1. 计算机科…

Kotlin 中的 `flatMap` 方法详解

在 Kotlin 中,flatMap 是一个非常强大的集合操作函数,它结合了 map 和 flatten 的功能。flatMap 能够将一个集合中的每个元素映射为另一个集合,然后将这些集合连接成一个单一的集合。在很多场景下,它比单独使用 map 和 flatten 更…

时空大数据平台:激活新质生产力的智慧引擎

在数字化转型的浪潮中,时空大数据平台以其独特的价值,成为推动新质生产力发展的关键力量。本文不仅深入剖析时空大数据平台的定义与内涵,探讨其在智慧城市、智慧农业、环境管理、应急管理等领域的应用成效,还将详尽阐述平台如何通…

iPhone 16系列:摄影艺术的全新演绎,探索影像新境界

在科技的浪潮中,智能手机摄影功能的进化从未停歇。 苹果公司即将推出的iPhone 16系列,以其卓越的相机升级和创新特性,再次站在了手机摄影的前沿。 从硬件到软件,从拍照体验到图像处理,iPhone 16系列都展现了其在移动…

Windows下安装部署RocketMQ

Windows下安装部署RocketMQ 引言下载安装rocketmq配置环境变量启动NameServer启动Brokerrocketmq控制台[rocketmq-dashboard](https://github.com/apache/rocketmq-dashboard)安装及配置修改rocketmq记录日志地址设置namesrv、broker堆栈内存RocketMQ注册为Windows服务start.ba…

pod基本概念

一 、Pod Pod 是 kubernetes 基本调度单位。每个 Pod 中可以运 行一个或多个容器,共享 Pod 的文件系统、IP 和网络等 资源,每个 Pod 只有一个 IP。 1. 创建 Pod 声明式文件方式创建 Pod,支持 yaml 和 json 创建 pod 测试访问 2. 更改 Pod 的…

解决phpstudy无法启动MySQL服务

三种方法 如果说你在小皮里面,启动mysql,发现启动不了,而且你在你自己电脑本地有装过mysql服务,那么可以按照我下面的不走来,按顺序试验1,2,3,三个里面肯定有一个是可以解决的 1.停止本地的mysq…

EasyExcel 快速入门

目录 一、 EasyExcel简介 官网链接: 代码链接: 二、 EasyExcel快速上手 引入依赖: 设置Excel相关注解 编写对应的监听类: 简单写入数据: 简单读取数据: 不需要使用监听器: 需要使…

视频工具EasyDarwin将本地视频生成RTSP给WVP拉流列表

效果 ffmpeg生成rtsp流 EasyDarwin的rtsp端口默认的是10054, 使用ffmpeg将本地视频转到EasyDarwin的rtsp。 F:\rtsp\ffmpeg-7.0.2-essentials_build\bin>ffmpeg -re -i F:\rtsp\123.mp4 -rtsp_transport tcp -vcodec h264 -f rtsp rtsp://127.0.0.1:10054/video11 它的直播…

Yocto - 使用Yocto开发嵌入式Linux系统_01 前言

Embedded Linux Development Using Yocto Project: Leverage the power of the Yocto Project to build efficient Linux-based products, Third Edition By: Otavio Salvador, Daiane Angolini Overview of this book Yocto 项目是开发可靠的嵌入式 Linux 项目的行业标准。与…

【网络安全】-文件包含漏洞-pikachu

文件操作漏洞包括文件上传漏洞,文件包含漏洞,文件下载漏洞。 文章目录 前言 : 什么是文件包含漏洞? 1.文件包含漏洞的分类: 本地文件包含漏洞: 远程文件包含漏洞: 2.两种文件包含漏洞的区别: 3.…

【资料分析】刷题日记2

第一套 √ 2013-2016一共有13,14,15,16四年,亦即16 - 13 1 4年 √ 是多少倍 ③vs④:都是只给出了年均增速,③求的是其中一年的,无法确定;④求的是这个时段总共的,可…

Leetcode 93-复原 IP 地址

有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔。 例如:“0.1.2.201” 和 “192.168.1.1” 是 有效 IP 地址,但是 “0.011.255.245”、“192.168.…

O1-preview:智能预测与预取驱动的性能优化处理器设计OPEN AI

# 创作不易,您的打赏、关注、点赞、收藏和转发是我坚持下去的动力! O1-preview 是一种用于性能优化的处理器设计原理,主要通过智能预测和数据预取来提升处理器的执行效率。以下是对 O1-preview 原理的详细介绍,以及它相对于以往的…

腾讯音乐 2024乐圃音乐空间夏令营:以音乐传递爱,点亮公益之光

8 月 25 日晚,在四川北川,一场充满无尽 “乐” 趣的结营音乐会,为 2024 年乐圃音乐空间夏令营画上了完美的句号。这个由腾讯音乐娱乐集团(Tencent Music Entertainment Group,以下简称 “TME”)旗下腾讯音乐…

上架谷歌安卓APP完整图文流程

本节包含以下内容: 第一步:登录Google play开发者后台第二步:创建应用第三步:设置应用第四步:开启通知第五步:发布应用第六步:查看审核结果第七步:配置app支付参数第八步&#xff1…