C# 设计模式(八)
二十八、对设计模式的批评
设计模式让你受益于他人的经验,这通常被称为经验重用。你学习他们如何解决问题,他们如何试图在他们的系统中采用新的行为,等等。一个模式可能并不完全适合你的工作,但是如果你在开始的时候专注于最佳实践以及模式的问题,你就更有可能做出更好的应用。这就是为什么我现在将讨论设计模式的批评。了解他们可以为你提供一些真正的价值。如果你在设计软件之前批判性地思考模式,你可以在某种程度上预测你的投资回报。让我们回顾一下一些开发人员经常提出的以下几点:
-
模式的概念来自克里斯托弗·亚历山大。他是一名建筑师,但不是计算机程序员。他考虑了多年来变化不大的领域(与软件行业相比)。相反,软件行业总是在变化,软件开发的变化比任何其他领域都要快。这就是为什么批评家经常说你不能从克里斯托弗·亚历山大考虑过的领域(建筑和城镇)出发。
-
与今天相比,你在编程早期编写程序的方式是非常不同的。与早期的编程相比,现在你享受到了更多的便利(例如,更大的存储空间,超快的计算能力等)。因此,当您基于旧的实践提取模式时,您对它们表现出额外的尊重。
-
许多模式都是相似的,每种模式都有利弊。(我在每章末尾的“问答环节”中讨论了它们。)一种情况下的缺点在另一种情况下可能是真正的优点。
-
由于软件行业的不断变化,今天给你带来满意结果的模式将来可能会成为你的一个大负担。
-
用有限数量的设计模式很难很好地设计出无限数量的需求。
-
设计软件是一门艺术。对于最好的艺术没有定义或标准。
-
设计模式给你的是想法,而不是实现(比如库和框架)。你知道每个人的思想都是独一无二的。因此,每个工程师可能对实现类似的概念有自己的偏好,如果心态差异很大,这可能会在团队中造成混乱。
-
考虑一个简单的例子。模式鼓励人们编写超类型(抽象类/接口)的代码。但是对于一个简单的应用,您知道没有即将到来的变化,或者应用只是为了演示的目的而创建的,这个想法可能对您没有太大的意义。
-
类似地,在一些较小的应用中,您可能会发现强制执行设计模式的规则会增加您的代码大小和维护成本。
-
抹去旧的,接受新的并不总是容易的。例如,当你第一次了解到遗传时,你很兴奋。你可能想以多种方式使用它,并且只看到这个概念的好处。但是后来,当您开始尝试设计模式时,您开始了解到在许多情况下,组合比继承更受欢迎。这种编程心态的转变并不容易。
-
设计模式基于一些关键原则,其中之一是识别可能变化的代码,然后将其从代码的其余部分中分离出来。从理论角度听起来很不错。但是在现实世界的实现中,谁能保证你的判断是完美的呢?软件行业总是在变化,它需要不断适应新的需求。
-
许多模式已经集成到现代语言中。您可以使用语言构造中的内置支持,而不是从头开始实现模式。
-
不恰当地使用模式会导致反模式(例如,不恰当地使用中介模式会导致 God 类反模式)。我在第二十九章中提供了反模式的概述。
-
许多人认为设计模式的概念只是表明一种编程语言可能需要一些额外的特性。因此,随着现代编程语言能力的提高,模式变得不那么重要。维基百科说,计算机科学家 Peter Norvig 认为,GoF 设计模式中的 23 种模式中有 16 种通过 Lisp 或 Dylan 的直接语言支持被简化或消除。你可以在
https://en.wikipedia.org/wiki/Software_design_pattern看到类似的想法。 -
我在本书中讨论的模式完全基于面向对象编程。这些模式的效率和适用性在其他领域是有问题的。
-
这些模式不可互换。
-
最后,设计模式帮助你从他人的经验中获益。你了解他们的想法;您开始了解他们是如何遇到挑战的,他们是如何在他们的系统中实现新行为的,等等。但是,如果你深入到基本思想,你会发现你开始的假设是,一个初学者或相对缺乏经验的人不能比他/她的前辈更好地解决问题。有时候,一个相对缺乏经验的人比他的前辈有更好的眼光,他证明自己更有效。
问答环节
有这些模式的目录吗?
我从 GoF 的 23 种设计模式开始,然后在本书中讨论了更多的模式。GoF 的目录被认为是最基本的模式目录。
许多其他目录侧重于域。波特兰模式库和希尔赛德集团的网站在这方面是众所周知的。你可以从这些资源中获得有价值的见解和想法。希尔赛德集团网站还提供了各种会议和研讨会的信息。
作为起点,你可以参观 https://wiki.c2.com/?PortlandPatternRepository 和 https://hillside.net/patterns/patterns-catalog 。
Note
在撰写本文时,本书中提到的 URL 运行良好,但它们可能会在未来发生变化。
28.2 你为什么对其他模式保持沉默?
这些是我个人的信念。
-
计算机科学会不断发展,你会不断得到新的模式。
-
如果您不熟悉基本模式,您就不能评估剩余的或即将到来的模式的真正需求。比如你很了解 MVC,你就能看出它和模型-视图-展示者(MVP)有什么不同,明白为什么需要 MVP。
-
这本书已经很大了。对每个模式更详细的讨论需要更多的页面,这将使本书太大而难以消化。
因此,在这本书里,我把重点放在在今天的编程世界中仍然相关的基本模式上。
28.3 我经常看到 力 这个词与设计模式的描述。这是什么意思?
这是开发人员证明其开发合理性的标准。概括地说,你的目标和当前的约束是你的力量的两个重要部分。因此,当您开发您的应用时,您可以用这些部分来证明您的开发。
在各种论坛上,我看到人们在为模式的定义争论不休,并且说“模式是在一个环境中对一个问题的一个被证明的解决方案。”这是什么意思?
这是一个简单易记的模式定义。但是简单地把它分成三个部分(问题、背景和解决方案)是不够的。
比如你在去机场的路上,你很着急。突然,你意识到你把登机牌落在家里了。我们来分析一下情况。
问题:你需要准时到达机场。
背景:你把登机牌落在家里了。
你可能想到的解决办法就是折返,赶回家拿登机牌。
这个解决方案可能只工作一次,但是你能重复应用同样的程序吗?你知道答案。这不是一个明智的解决方案,因为它取决于你目前有多少时间回家拿登机牌,然后到达机场。还要看现在的流量等很多因素。所以,即使你成功了一次,你也要为将来类似的情况准备更好的解决方案。
努力学习意思、意图、上下文等等,以便清楚地理解一个模式。
当我看到两种不同模式的相似 UML 图时,我感到困惑。此外,在许多情况下,我还对模式的分类感到困惑。
这是非常自然的。您越是阅读和分析这些实现,越是试图理解这些设计背后的意图,它们之间的区别就会变得越清晰。
我应该什么时候考虑写一个新的模式?
写一个新的模式并不容易。您需要研究和评估可用的模式。但是如果您找不到现有的模式来满足您特定领域的需求,您可能需要编写自己的模式。如果您的解决方案通过了三个的规则,这是最好的,该规则说,要实现一个标签模式,一个解决方案需要在现实世界的解决方案中成功应用至少三次。一旦你做到了这一点,你就可以让其他人知道它,参与讨论论坛,并从其他人那里得到反馈。这项活动对您和开发社区都有帮助。
二十九、反模式
不讨论反模式,设计模式的讨论就不完整。下一章简要概述了反模式。我们开始吧。
概观
在现实世界的应用开发中,有时您可能会遵循一些在开始时非常吸引人的方法,但是从长远来看,它们会产生问题。例如,你可以尝试快速解决问题,以满足交货期限。但是如果你没有意识到潜在的陷阱,你可能需要为这些错误付出巨大的代价。
反模式提醒您可能导致问题的糟糕解决方案的常见错误,以便您可以采取预防措施。“预防胜于治疗”这句谚语适用于这种情况。
Points to Remember
反模式通过描述有吸引力的方法如何在将来使您的生活变得困难,提醒您常见的错误。与此同时,他们提出了一些替代解决方案,这些方案在开始时可能看起来很难,但最终会帮助你建立一个更好的解决方案。简而言之,反模式识别既定实践中的问题,它们可以将一般情况映射到特定类别的高效解决方案。他们还可以为你提供更好的计划来扭转一些不好的做法,使这些健康的解决方案。
反模式简史
设计模式的最初想法来自建筑建筑师克里斯托弗·亚历山大,他是伯克利的教授。他分享了在规划良好的城镇中建造建筑的想法。渐渐地,这些概念进入了软件开发,并通过像沃德·坎宁安和肯特·贝克这样的前沿软件开发人员而流行起来。1994 年,通过一个名为程序设计模式语言(PLoP)关于设计模式的行业会议,设计模式的思想进入了面向对象软件开发的主流。Hillside Group 主办了它,Jim Coplien 的论文“一种开发过程生成模式语言”因其上下文而闻名。随着 GoF 推出经典教材Design Patterns:Elements of Reusable Object-Oriented Software,设计模式的思想变得非常流行。
毫无疑问,这些伟大的设计模式帮助了(并且仍然在帮助)程序员开发高质量的软件。但在某些情况下,人们也开始注意到负面影响。这里有一个常见的例子。许多开发人员想要展示他们的专业知识,而没有对这些模式在他们特定领域的真实评估或结果。作为一个明显的副作用,模式被植入了错误的上下文,产生了低质量的软件,并最终导致了对开发人员或他们的组织的巨大惩罚。
因此,软件行业需要关注类似错误的负面后果,最终,反模式的思想得到了发展。许多专家开始在这一领域做出贡献,但是第一个结构良好的模型来自 Michael Akroyd 题为“反模式:针对对象误用的疫苗”的演讲这是 GoF 设计模式的对外观。
术语反模式随着威廉·布朗等人的书反模式:重构软件、架构和危机中的项目而流行起来。以下摘自该书。
因为反模式有如此多的贡献者,将反模式的最初想法分配给单一来源是不公平的。相反,反模式是补充设计模式运动和扩展设计模式模型的自然步骤。
反模式的例子
这些是反模式及其背后的概念/思维模式的一些例子。
-
过度使用模式开发人员可能会不惜任何代价尝试使用模式,不管它是否合适。
-
神类试图用许多不相关的方法控制几乎一切的大物体。不恰当地使用中介模式可能会导致反模式。
-
不是在这里发明的我是一家大公司,我想从零开始打造一切。虽然已经有一个由另一家公司开发的库,但我不会使用它。我会自己做所有的东西,一旦开发出来,我会用我的品牌价值宣布,“嘿伙计们,终极图书馆已经为你们推出了。”
-
零表示空举个常见的例子,开发者认为没有人愿意在纬度零、经度零。另一种常见的变化是当程序员使用–1,999 或类似的数字来表示不合适的整数值。另一个错误的用例是用户在应用中将“09/09/9999”视为空日期。因此,在前面的例子中,如果用户需要数字-1 或 999,或者日期“09/09/9999”,他将得不到。
-
金锤X 先生相信技术 T 永远是最好的。所以,如果他需要开发一个新系统(这需要新的学习),他会更喜欢 T,即使它不合适。他想,“我很忙。如果我能设法应付,我就不需要再学习任何技术了。”
-
拍信使我已经有压力了,节目截止日期快到了。测试人员 John 总是能找到难以修复的典型缺陷。另外,约翰不喜欢我,所以他喜欢在我的代码中寻找缺陷。所以,在这个阶段,我不想把他牵扯进来;他会发现更多的缺陷,而我会错过目标期限。
-
瑞士军刀对能够满足客户各种需求的产品的需求,比如可以治愈所有疾病的药物,为具有不同需求的广大客户服务的软件——界面有多复杂并不重要。
-
复制粘贴编程我需要解决一个问题,但我已经有了一段处理类似情况的代码。因此,我可以复制工作的旧代码,然后在需要时修改它。但是当你从一个现有的拷贝开始时,你基本上继承了所有与之相关的潜在缺陷。此外,如果将来需要修改原始代码,您需要在多个地方实现修改。这种做法也违反了不重复自己(干)的原则。
-
架构师不编码我是一名架构师。我的时间很宝贵。我只展示路径或做关于编码的精彩讲座。有足够多的实施者应该实施我的想法。架构师打高尔夫也是这个反模式的姐妹。
-
隐藏和悬停不要显示所有编辑或删除链接,直到用户悬停在元素上。
-
伪装的链接和广告愚弄你的用户,当他们点击一个链接或一个广告时,他们就获得收入,尽管他们不能得到他们想要的。
-
数字管理提交次数越多,代码行数越多,或者缺陷修复量越大,都是优秀开发人员的标志。
用代码行来衡量编程进度,就像用重量来衡量飞机制造进度。
—比尔·盖茨
Points to Note
-
如今,您可以从不同的网站/来源了解各种反模式;比如
https://en.wikipedia.org/wiki/Anti-pattern。 -
您还可以在
http://wiki.c2.com/?AntiPatternsCatalog获得反模式目录的详细列表。 -
您可能还会注意到,反模式的概念并不局限于面向对象的编程。
反模式的类型
反模式可以属于不同的类别。甚至一个典型的反模式也可以属于多个类别。以下是一些常见的分类。
-
架构反模式瑞士军刀反模式就是这一类的例子。
-
开发反模式上帝类,过度使用模式就是这一类的例子。
-
管理反模式拍摄信使反模式就属于这一类。
-
组织反模式架构师不编码,打高尔夫的架构师属于这一类。
-
用户界面反模式的例子包括伪装的链接/广告。
Note
伪装的链接/广告也被称为暗模式。
问答环节
29.1 反模式与 设计模式 有什么关系?
当你使用设计模式时,你重用了在你之前的人的经验。当你开始仅仅为了使用而盲目地使用这些概念时,你就陷入了重复使用循环解决方案的陷阱。这可能会导致您以后陷入糟糕的境地,然后您会发现您的投资回报率(ROI)在下降,但维护成本却在增加。简单来说,简单而有吸引力的解决方案(或模式)可能会在将来给你带来更多的问题。
29.2 设计模式可能会变成反模式。这是正确的吗?
是的,如果你在一个错误的环境中应用一个设计模式,会导致比它所解决的问题更多的麻烦,最终它会变成一个反模式。所以,在你开始之前,理解问题的本质和背景是非常重要的。
29.3 反模式仅与软件开发人员相关。这是正确的吗?
不。反模式的用处不仅限于开发人员。它可能适用于其他人;例如,它对管理人员和技术架构师也很有用。
29.4 即使你现在没有从反模式中获得太多好处,这些也可以帮助你在将来以更低的维护成本轻松地适应新的特性。这是正确的吗?
是的。
29.5 反模式的 原因 可能有哪些?
它们可能来自不同的来源或心态。下面列出了一些某人可能会说(或想)的常见例子。
-
“我们需要尽快交付产品。”
-
“我们与客户的关系非常好。因此,目前我们不需要分析未来的影响。”
-
“我是重用专家。我非常了解设计模式。”
-
“我们使用最新的技术和功能来打动我们的客户。我们不需要担心遗留系统。”
-
"更复杂的代码反映了我在这方面的专业知识."
29.6 你能列出一些反模式的 症状 吗?
在面向对象编程(OOP)中,最常见的症状是您的系统不容易适应新的特性。此外,维护成本持续增加。您可能还会注意到,您已经失去了关键的面向对象特性的能力,比如继承、多态等等。
除此之外,您可能会看到以下症状。
-
全局变量的使用
-
代码复制
-
有限/没有代码重用
-
一大类(神类)
-
大量的无参数方法等。
29.7 如果检测到反模式,有什么补救措施?
你可能需要重构你的代码,找到一个更好的解决方案。例如,以下是一些针对以下反模式的解决方案。
-
金锤试着通过一些适当的训练来教育 X 先生。
-
零表示空值使用一个额外的布尔变量,对你来说更明智的是正确地表示空值。
-
数字管理如果你明智地使用数字,数字就是好的。你不能根据一个程序员每周修复的缺陷数量来判断他的能力。质量也很重要。一个典型的例子是,修复一个简单的 UI 布局比修复系统中严重的内存泄漏要容易得多。考虑另一个例子。“大量测试通过”并不意味着您的系统更加稳定,除非这些测试使用不同的代码路径/分支。
-
拍摄信使欢迎测试员约翰,并立即让他参与进来。不要把他当作你的对手。你可以适当地分析他的发现,并尽早修复真正的缺陷,以避免最后一刻的惊喜。
-
复制粘贴编程你可以重构代码,而不是寻找快速的解决方案。您还可以创建一个公共位置来维护常用的方法,以避免重复并使维护更容易。
-
架构师不编码让架构师参与实现阶段的某些部分。这对组织和架构师都有帮助。这让他们对产品的真正功能有了更清晰的了解。这个过程也有助于他们重视你的努力。
29.8 什么叫 重构 ?
在编码领域,术语重构意味着改进现有代码的设计,而不改变系统/应用的外部行为。这个过程有助于您获得更具可读性的代码。同时,这些代码应该更能适应新的需求(或者变更请求),并且更易于维护。
三十、常见问题解答
本章是本书所有章节的“问答”部分的子集。这些问题中有许多没有在具体的章节中讨论,因为相关的模式还没有涉及到。除了下面的问答,我强烈推荐你浏览本书的所有“问答”部分,以便更好地理解这些模式。
30.1 你最喜欢哪个 的设计模式 ?
这取决于许多因素,如背景、情况、需求、制约因素等等。如果你知道所有的模式,你会有更多的选择。
30.2 开发人员为什么要使用设计模式?
一个常见的答案是,它们是现实软件开发中重复出现的软件设计问题的可重用解决方案。但是我之前提到过(比如在第二十八章的问答环节),你需要分析各方面的情况,比如在你实现一个模式之前,问题的上下文和意图。
30.3 命令模式和纪念模式有什么区别?
命令模式存储所有的动作,但是记忆模式只在请求时保存状态。此外,命令模式可以支持每个动作的撤销操作,但是 Memento 模式不需要这样。我强烈建议你访问第十九章的问答 19.4,清楚地了解其中的区别。
30.4 门面模式和建造者模式有什么区别?
Facade 模式旨在使代码的特定部分更易于使用。它从开发者那里抽象出细节。
构建器模式将对象的构造与其表示分离开来。在第三章中,导演调用了同样的方法,Construct() in演示 1 和ConstructCar()演示 2,来创造不同类型的车辆。换句话说,您可以使用相同的构造过程来创建多种类型。
30.5 构建者模式和策略模式有什么区别?它们有相似的 UML 表示。
首先,你必须检查意图。构建者模式属于创造模式的范畴,而策略模式属于行为模式的范畴。他们关注的领域不同。当你考虑构建器模式时,你可以使用相同的构建过程来创建不同的类型,当你使用策略模式时,你可以在运行时自由选择算法。
30.6 命令模式和 解释器模式 有什么区别?
在命令模式中,命令是对象。在解释器模式中,命令是句子。在解释器模式中,您可以制定自己的评估规则并构建语法树。对于一个简单的语法来说,这很好,但是当你的语法很复杂的时候,就很难实现了。这是因为建立一个解释器的成本对你来说是一个大问题。
30.7 责任链模式和观察者模式有什么区别?
对于 Observer 模式,所有注册用户都被并行通知或收到请求(主题的更改)。对于责任链模式,您可能不会到达链的末端,因此所有用户不需要处理相同的场景。位于链开始的某个用户可以更早地处理请求。建议你参考问答 14.4。
30.8 责任链模式和装饰者模式的区别是什么?
它们一点也不一样,但你可能认为它们的结构相似。像 FAQ 30.7 一样,在责任链模式中,通常只有一个类处理一个请求,但是在装饰模式中,所有的类都处理一个请求。您必须记住,装饰器只在添加和删除责任的上下文中有效。如果您可以将装饰模式与单一责任原则结合起来,那么您就可以在运行时添加(或删除)单一责任。
30.9****调停者模式 和 观察者模式 有什么区别?
GoF 说,“这是相互竞争的模式。它们之间的区别在于,观察者通过引入观察者和主体对象来分发通信,而中介对象封装了其他对象之间的通信。”
这里我建议你考虑一下第二十一章中的中介模式的例子。在演示 2 中,我解释了如果发送者在线,他可以接收发送给目标接收者的消息。我描述了如何限制一个局外人并提高安全性。但是在观察者模式中,主体/广播者通常不关心其观察者的状态。它只是广播消息。
GoF 的书告诉我们,在制作可重用的观察者和主体时,你可能比制作可重用的中介者面临更少的挑战,但是关于交流的流程,中介者比观察者得分更高。
30.10****单胎班 和 静态班 你更喜欢哪个?
看情况。首先,您可以创建单例类的对象,这对于静态类是不可能的。因此,继承和多态的概念可以用单例类来实现。此外,一些开发人员认为在现实世界的应用中模仿静态类(例如,考虑单元测试场景)是具有挑战性的。
30.11 如何区分 代理 和 适配器 ?
代理在与它们的主体相似的接口上工作。适配器在不同的接口上工作(对于它们所适配的对象)。
代理和装饰者有什么不同?
有不同类型的代理,它们因实现而异。因此,这些实现中的一些可能接近装饰者。例如,保护代理可以像装饰器一样实现。但是你必须记住,装饰者专注于增加责任,而代理者专注于控制对对象的访问。
30.13 中介与门面有何不同?
总的来说,两者都简化了复杂的系统。在中介模式中,中介和内部子系统之间存在双向连接。相反,在 Facade 模式中,您通常提供单向连接(子系统不知道 Facade)。
30.14 享元模式和国家模式之间有什么联系吗?
GoF 书提到 Flyweight 模式可以帮助您决定何时以及如何共享状态对象。
30.15****简单工厂 、 工厂方法 、 抽象工厂设计模式 有什么相似之处?
所有这些都封装了对象创建,这意味着您要对抽象(接口)而不是具体的类进行编码。简单地说,每个工厂都通过减少对具体类的依赖来促进松散耦合。
简单工厂、工厂方法和抽象工厂设计模式之间有什么区别?
这是你在各种工作面试中可能会面临的一个重要问题。首先参考第五章中的问答 5.3,如果需要,遍历第 4 和 5 章中的所有问答环节。
如何区分****和** 工厂方法模式 ?**
**单例模式确保您每次都能获得一个唯一的实例。它还限制创建其他实例。
但是工厂方法模式并没有说你只能得到一个唯一的实例。通常,这种模式会根据您的需要创建任意多的实例,并且这些实例不一定是唯一的。这些新类型化的实例可以实现一个公共基类。(请记住,根据 GoF 定义,工厂方法让一个类将实例化推迟到子类。)
30.18****模板方法模式 与策略模式有何不同?
在策略模式中,您可以使用委托来改变整个算法。另一方面,使用 Template Method 模式,您只改变了使用继承的算法中的某些步骤,但是算法的整体流程是不变的。
30.19 如何区分 访客模式 和策略模式?
在策略模式中,每个子类使用不同的算法来解决一个共同的问题。但是在访问者设计模式中,每个访问者子类可以提供彼此不同的功能。
30.20 空对象和代理有什么不同?
一般来说,代理在某个时间点作用于真实对象,它们也可能提供一些行为。但是空对象不做任何这样的操作。
如何区分解释者模式和访问者模式?
使用解释器模式,您可以将简单的语法表示为一个对象结构,但是在访问者模式中,您可以定义一些想要在对象结构上使用的特定操作。除此之外,解释器可以直接访问所需的属性,但是在访问者模式中,您需要特殊的功能(类似于观察者)来访问它们。
30.22 如何区分 Flyweight 模式 和 对象池模式 ?
我没有在本书中讨论对象池模式。但是如果你已经知道对象池模式,你会注意到在 Flyweight 模式中,flyweights 可以有内部和外部状态。因此,如果一个 flyweight 有两种状态,那么它的状态是分开的,客户端需要将一部分状态传递给它。此外,通常,客户端不会更改固有状态,因为它是共享的。
对象池不存储外部状态的任何部分;所有状态信息都存储/封装在池化对象中。此外,客户端可以更改池化对象的状态。
30.23 库或框架与设计模式有何相似和不同之处?
它们不是设计模式。它们提供了可以直接在应用中使用的实现。但是他们可以在那些实现中使用模式的概念。
30.24 什么是 回调法 ?
它是一种在您执行一些特定操作后可以调用的方法。您将经常在异步编程中看到这种方法的使用,当您不知道前一个操作的确切完成时间,但希望在前一个任务结束后开始某个特定任务时,这种方法会很有用。你应该参考第二十七章中的演示 7 来更好地理解它。**