Java-设计模式-六-

89 阅读26分钟

Java 设计模式(六)

原文:Java Design Patterns

协议:CC BY-NC-SA 4.0

二十七、对设计模式的批评

在这一章中,我提出了一些对设计模式的批评。阅读批评能提供真正的价值。如果你在设计软件之前批判性地思考模式,你可以在某种程度上预测你的“投资回报”。设计模式基本上帮助你从他人的经验中获益。这就是通常所说的经验复用。你学习他们如何解决挑战,他们如何试图在他们的系统中适应新的行为,等等。一个模式可能并不完全适合你的工作,但是如果你在开始的时候专注于最佳实践以及模式的问题,你就更有可能做出更好的应用。

以下是对模式的一些批评。

  • Christopher Alexander 考虑了多年来变化不大的领域(与软件行业相比)。相反,软件行业总是在变化,软件开发的变化比任何其他领域都要快。所以,你不能从克里斯托弗·亚历山大考虑的领域(建筑和城镇)出发。

  • 在今天的世界里,你写程序的方式是不同的,你现在拥有的设备比过去的编程时代要多得多。因此,当您基于一些旧的实践提取模式时,您基本上对它们表现出了额外的尊重。

  • 许多模式彼此接近。每种模式都有利弊(我在每章末尾的“问答环节”中讨论过它们。)一种情况下的缺陷在另一种情况下可能是真正的优点。

  • 今天给你带来满意结果的模式,在不久的将来会成为你的一个大负担,因为软件行业的“持续变化”。

  • 用有限数量的设计模式很难设计出无限数量的需求。

  • 设计一个软件基本上是一门艺术。没有最好的艺术的定义或标准。

  • 设计模式给你的是想法,而不是实现(比如库或框架)。每个人的思想都是独特的。因此,每个工程师可能都有自己的偏好来实现类似的概念,这可能会在团队中造成混乱。

  • 考虑一个简单的例子。模式鼓励人们编写超类型(抽象类/接口)的代码。但是对于一个简单的应用程序,您知道没有即将到来的变化,或者应用程序只是为了演示的目的而创建的,这个规则可能没有多大意义。

  • 同样,在一些小型应用程序中,您可能会发现强制执行设计模式的规则会增加您的代码大小和维护成本。

  • 抹去旧的适应新的并不总是容易的。例如,当你第一次了解到遗传时,你很兴奋。你可能想以多种方式使用它,并且只看到这个概念的好处。但是后来当你开始尝试设计模式时,你开始了解到在许多情况下,组合比继承更受青睐。这种换挡并不容易。

  • 设计模式基于一些关键原则,其中之一是识别可能变化的代码,然后将其从代码的其余部分中分离出来。从理论角度听起来很不错。但是在现实世界中,谁能保证你的判断是完美的呢?软件行业总是在变化,它需要适应新的需求。

  • 许多模式已经与现代语言集成在一起。您可以使用语言构造中的内置支持,而不是从头开始实现模式。例如,您可能会注意到,在某些上下文中,每个模式都有 JDK 实现。

  • 不恰当地使用模式会导致反模式(例如,不恰当地使用中介模式会导致“上帝级”反模式)。我在第二十八章中给出了反模式的简要概述。

  • 许多人认为设计模式的概念只是表明一种编程语言可能需要额外的特性。因此,随着现代编程语言能力的增强,模式变得不那么重要了。维基百科说,计算机科学家 Peter Norvig 认为,通过 Lisp 或 Dylan 的直接语言支持,GoF 设计模式中的 23 种模式中有 16 种被简化或消除。在 https://en.wikipedia.org/wiki/Software_design_pattern 可以看到一些类似的想法。

  • 最后,设计模式基本上帮助你从别人的经验中获益。你正在了解他们的想法,你开始知道他们是如何遇到挑战的,他们是如何试图在他们的系统中适应新的行为,等等。但是你首先假设一个初学者或相对缺乏经验的人不能比他/她的前辈更好地解决问题。在一些特定的场合,一个相对来说经验不足的人可以比他的前辈有更好的眼光,他可以在未来更有效的证明自己。

问答环节

  1. 有这些模式的目录吗?

    我从 GoF 的 23 种设计模式开始,然后在本书中讨论了另外三种模式。GoF 的目录被认为是最基本的模式目录。

    但是肯定有许多其他目录专注于特定的领域。

    波特兰模式库和希尔赛德集团的网站在这方面是众所周知的。你可以在http://wiki.c2.com/?WelcomeVisitorshttps://hillside.net/patterns/patterns-catalog从这些资源中获得宝贵的见解和思想。

    希尔赛德集团的网站还提到了它的各种会议和研讨会。

注意

在撰写本文时,书中的 URL 运行良好,但其中一些链接和访问这些链接的策略可能会在未来发生变化。

  1. 你为什么不涵盖其他模式?

    These are my personal beliefs:

    • 计算机科学不断发展,你不断得到新的模式。

    • 如果您不熟悉基本模式,您就不能评估剩余的或即将到来的模式的真正需求。比如,如果你很了解 MVC,你就能看出它和模型-视图-展示者(MVP)有什么不同,明白为什么需要 MVP。

    • 这本书已经很厚了。对每个模式的详细讨论需要更多的页面,这将使这本书太大而难以消化。

因此,在这本书里,我把重点放在在今天的编程世界中仍然相关的基本模式上。

  1. 我经常在描述设计模式时看到“力”这个词。这是什么意思?

    这是开发人员证明其开发合理性的标准。概括地说,你的目标和当前的约束是你的力量的两个重要部分。因此,当您开发您的应用程序时,您可以用这些部分来证明您的开发。

  2. 在各种论坛上,我看到人们为模式的定义争论不休,并且说“模式是在一个环境中对一个问题的一个被证实的解决方案。”这是什么意思?

    这是一个简单易记的模式定义。但是简单地把它分成三个部分(问题、背景和解决方案)是不够的。

    举个例子,假设你要去机场,你很匆忙。突然,你发现你把登机牌落在家里了。我们来分析一下情况:

    问题 :你需要准时到达机场。

    语境 :把登机牌落在家里了。

    可能想到的解决办法 :折返,高速,冲回家拿登机牌。

    这个解决方案可能只工作一次,但是你能重复地应用同样的程序吗?你知道答案。这不是一个明智的解决方案,因为它取决于你有多少时间从家里领取通行证并返回机场。还要看目前路上的交通等很多因素。所以,即使你能获得一次成功,你也要为将来类似的情况准备一个更好的解决方案。

    所以,试着理解意思、意图、上下文等等,以便清楚地理解一个模式。

  3. 有时我会对两种不同模式的相似 UML 图感到困惑。此外,在许多情况下,我还对模式的分类感到困惑。我该如何克服这一点?

    这是非常自然的。您越是阅读和分析这些实现,越是试图理解设计背后的意图,它们之间的区别就会越清晰。

  4. 什么时候我应该考虑写一个新的模式?

    写一个新的模式并不容易。在投入精力之前,您需要大量研究并评估可用的模式。但是如果您找不到任何现有的模式来满足您特定领域的需求,您可能需要编写自己的模式。如果您的解决方案通过了“三原则”(基本上是说,要获得标签“模式”,一个解决方案需要在现实世界的解决方案中成功应用至少三次),那就非常好了。一旦你做到了这一点,你就可以让其他人知道它,参与讨论论坛,并从其他人那里得到反馈。这项活动对您和开发社区都有帮助。

二十八、反模式:避免常见错误

没有反模式,设计模式的讨论就不完整。本章简要概述了反模式。我们开始吧。

什么是反模式?

在现实世界的应用程序开发中,您可能会遵循最初非常吸引人的方法,但是从长远来看,它们会带来问题。例如,你试图快速解决问题,以满足交货期限,但如果你没有意识到潜在的陷阱,你可能会付出很大的代价。

反模式提醒您会导致糟糕解决方案的常见错误。了解它们有助于你采取预防措施。“预防胜于治疗”这句谚语非常适合这种情况。

注意

反模式通过描述有吸引力的方法如何使您未来的生活变得困难,提醒您常见的错误。与此同时,他们提出了一些替代解决方案,这些方案在开始时可能看起来很难,但最终会帮助你建立一个更好的解决方案。简而言之,反模式识别既定实践中的问题,它们可以将一般情况映射到特定类别的高效解决方案。他们还可以提供更好的计划来扭转一些不良做法,并制定健康的解决方案。

反模式简史

设计模式的最初想法来自建筑建筑师 Christopher Alexander。他分享了他在规划良好的城镇中建造建筑物的想法。渐渐地,这些概念进入了软件开发,并通过像沃德·坎宁安和肯特·贝克这样的前沿软件开发人员而流行起来。1994 年,通过一个关于设计模式的行业会议,设计模式的思想进入了主流的面向对象软件开发,被称为程序设计的模式语言(PLoP)。它是由希尔赛德集团主办的。吉姆·科普林的论文《生成模式语言的发展过程》就是在这种背景下发表的著名论文。随着四人帮推出经典文本设计模式:可重用面向对象软件的元素,设计模式变得非常流行。

毫无疑问,这些伟大的设计模式帮助了(并且仍然在帮助)程序员开发高质量的软件。但是人们也开始注意到负面影响。一个常见的例子是,许多开发人员想要展示他们的专业知识,而没有对这些模式在其特定领域中的结果进行真正的评估。作为一个明显的副作用,模式被植入了错误的上下文中,这产生了低质量的软件,并最终对开发者和他们的公司造成了巨大的损失。

因此,软件行业需要关注这些错误的负面后果,最终,反模式的思想得到了发展。许多专家开始在这一领域做出贡献,但是第一个结构良好的模型来自 Michael Akroyd 的演讲“反模式:针对对象误用的疫苗”这是 GoF 设计模式的对外观。

术语反模式在他们的书反模式:重构软件、架构和危机中的项目 (Wiley,1998)中变得流行起来。后来,斯科特·托马斯加入了他们的小组。他们说,

由于反模式有如此多的贡献者,将反模式的最初想法分配给单一来源是不公平的。相反,反模式是补充设计模式运动和扩展设计模式模型的自然步骤。

反模式的例子

下面是一些反模式及其背后的概念/思维模式的例子。

  • 过度使用模式:开发人员可能会不惜任何代价尝试使用模式,不管它是否合适。

  • 神类:试图用许多不相关的方法控制几乎一切的大物体。不恰当地使用中介模式可能会导致这种反模式。

  • 不是在这里发明的:我是一家大公司,我希望一切从零开始。虽然已经有一个由小公司开发的库,但我不会使用它。我会自己做所有的东西,一旦开发出来,我会用我的品牌价值宣布,“嘿,伙计们。终极图书馆为你而生。

** 零表示空:一个常见的例子包括开发者认为没有人想在纬度零,经度零。另一种常见的变化是程序员使用:1,999 或类似的数字来表示不合适的整数值。当用户在应用程序中将“09/09/9999”视为空日期时,会观察到另一个错误的用例。因此,在这些情况下,如果用户需要数字:1,999 或日期“09/09/9999”,他将无法获得这些数字。

*   *金锤*:X 先生相信技术 T 永远是最好的。所以,如果他需要开发一个新系统(这需要新的学习),他仍然更喜欢 T,即使它不合适。他认为,“如果我能用 t 来管理它,我就不需要学习更多的技术了。”

*   *数字管理*:提交次数越多,代码行数越多,或者修复的缺陷越多,都是伟大开发者的标志。比尔·盖茨说过,“用代码行数来衡量编程进度,就像用重量来衡量飞机制造进度一样。”

*   *拍信使*:你已经有压力了,节目截止日期快到了。有一个聪明的测试人员,他总能找到难以修复的典型缺陷。所以,在这个阶段,你不想让他参与进来,因为他会发现更多的缺陷,可能会错过最后期限。

*   *瑞士军刀*:要求产品能够满足顾客的各种需求。或者制造一种可以治愈所有疾病的药物。或者设计服务于各种不同需求的客户的软件。界面有多复杂并不重要。

*   *复制粘贴编程*:我需要解决一个问题,但是我已经有了一段代码来处理类似的情况。因此,我可以复制当前正在运行的旧代码,并在必要时开始修改它。但是当你从一个现有的拷贝开始时,你基本上继承了所有与之相关的潜在缺陷。此外,如果将来需要修改原始代码,您需要在多个地方实现修改。这种做法也违反了*不重复自己*的原则。

*   *架构师不编码*:我是一个架构师。我的时间很宝贵。我将只展示路径或给出一个关于编码的伟大演讲。有足够多的实现者应该实现我的想法。*架构师打高尔夫*是这个反模式的姐妹。

*   *隐藏并悬停*:不要暴露所有编辑或删除链接,直到他/她悬停在元素上。

*   *伪装链接和广告*:当用户点击一个链接或一个广告时获得收入,但他们不能得到他们想要的。* 

*### 注意

现在,您可以从不同的网站/来源了解各种反模式。例如,一个维基百科页面谈到了各种反模式(参见 https://en.wikipedia.org/wiki/Antipattern ) *。*您还可以在 http://wiki.c2.com/?AntiPatternsCatalog 获得反模式目录的详细列表,以了解更多信息。您可能还会注意到,反模式的概念并不局限于面向对象的编程。

反模式的类型

反模式可以属于多个类别。甚至一个典型的反模式也可以属于多个类别。

以下是一些常见的分类。

  • 架构反模式:瑞士军刀反模式就是这一类的例子。

  • 开发反模式:God 类和过度使用模式就是这一类的例子。

  • 管理反模式:拍摄信使反模式就属于这一类。

  • 组织反模式:架构师不编码,架构师打高尔夫就是这一类的例子。

  • 用户界面反模式:例子包括伪装的链接和广告。

注意

伪装链接和广告也被称为黑暗模式

问答环节

  1. 反模式和设计模式有什么关系?

    使用设计模式,您可以重用前人的经验。当你开始仅仅为了使用而盲目地使用这些概念时,你就陷入了重复使用循环解决方案的陷阱。这会让你陷入糟糕的境地。然后你会发现你的投资回报在不断减少,但维护成本却在不断增加。表面上简单和标准的解决方案(或模式)可能会在将来给你带来更多的问题。

  2. 设计模式可能会变成反模式。这是正确的吗?

    是的。如果你在错误的环境中应用了一个设计模式,那会带来比它所解决的问题更多的麻烦。最终它会变成一个反模式。所以,理解问题的本质和背景是非常重要的。

  3. 反模式只与软件开发人员相关。这是正确的吗?

    不。反模式的用处不仅限于开发人员;它可能也适用于其他人;例如,反模式对经理和技术架构师也很有用。

  4. 即使您现在没有从反模式中获得太多好处,它们也可以帮助您在将来以更少的维护成本轻松适应新的特性。这是正确的吗?

    是的。

  5. 反模式的 原因 可能有哪些?

    It can come from various sources/mindsets. The following are a few common examples.

    • “我们需要尽快交付产品。”

    • “我们现在不需要分析影响。”

    • “我是重用专家。我非常了解设计模式。”

    • “我们将使用最新的技术和功能来打动我们的客户。我们不需要关心传统系统。”

    • "更复杂的代码将反映我在这方面的专业知识."

  6. 讨论一些 症状 的反模式。

    在面向对象编程中,最常见的症状是您的系统不能轻易地适应新的特性。此外,维护成本也在不断增加。您可能还会注意到,您已经失去了关键的面向对象特性的能力,比如继承、多态等等。

    Apart from these, you may notice some/all of the following symptoms.

    • 全局变量的使用

    • 代码复制

    • 有限/没有代码重用

    • 一大类(神类)

    • 大量的无参数方法等。

  7. 如果检测到反模式,有什么 补救措施

    您可能需要遵循重构的更好的解决方案。例如,下面是一些避免反模式的解决方案。

    金锤:你可以尝试通过适当的训练来教育 X 先生。

    零表示空值:你可以使用一个额外的布尔变量来恰当地表示空值。

    数字管理:如果你明智地使用数字,数字就是好的。你不能通过一个程序员每周修复的缺陷数量来判断他/她的能力。质量也很重要。一个典型的例子包括修复一个简单的 UI 布局比修复系统中的一个严重的内存泄漏要容易得多。考虑另一个例子。“更多数量的测试通过”并不意味着您的系统更加稳定,除非测试使用不同的代码路径/分支。

    拍摄信使:欢迎测试人员并立即让他参与进来。他可以在早期发现典型的缺陷,你可以避免最后一刻的意外。

    复制粘贴编程:你可以重构你的代码,而不是寻找一个快速的解决方案。您还可以创建一个公共场所来维护经常使用的方法,以避免重复并提供更容易的维护。

    架构师不编码:让架构师参与部分实现阶段。这对组织和架构师都有帮助。这项活动可以让他们更清楚地了解产品的真正功能。

  8. 你说的重构是什么意思?

    在编码领域,术语重构意味着改进现有代码的设计,而不改变系统/应用程序的外部行为。这个过程有助于您获得更具可读性的代码。同时,代码应该更能适应新的需求(或者变更请求),并且更易于维护。*

二十九、常见问题

本章是本书所有章节中“问答”部分的子集。这些问题中有许多没有在某些章节中讨论,因为相关的模式还没有涉及到。因此,除了下面的问答,强烈建议您通读本书的所有“问答”部分,以便更好地理解所有模式。

  1. 你最喜欢哪种设计模式?

    这取决于许多因素,如背景、情况、需求、制约因素等等。如果你知道所有的模式,你有更多的选择。

  2. 开发人员为什么要使用设计模式?

    它们是对现实软件开发中反复出现的软件设计问题的可重用解决方案。

  3. 命令模式和纪念模式有什么区别?

    命令模式存储所有的动作,但是记忆模式只在请求时保存状态。此外,命令模式对每个动作都有撤销和重做操作,但是 memento 模式不需要这样。

  4. facade 模式和 builder 模式有什么区别?

    facade 模式的目的是使代码的特定部分更容易使用。它从开发者那里抽象出细节。

    构建器模式将对象的构造与其表示分离开来。在第三章中,导演调用相同的construct()方法来创造不同类型的车辆。换句话说,您可以使用相同的构造过程来创建多种类型。

  5. 构建者模式和策略模式有什么区别?它们有相似的 UML 表示。

    你首先需要理解意图。构建者模式属于创造模式的范畴,而策略模式属于行为模式的范畴。他们关注的领域不同。使用构建器模式,您可以使用相同的构造过程来创建多种类型,而使用策略模式,您可以在运行时自由选择算法。

  6. 命令模式和解释器模式有什么区别?

    在命令模式中,命令基本上是对象。在解释器模式中,命令是句子。有时候解释器模式可能看起来很方便,但是您必须记住构建一个解释器的成本。

  7. 责任链模式和观察者模式有什么区别?

    在 observer 模式中,所有注册用户都被并行通知/获取请求(主题的更改),但是在 chain-of-responsibility 模式中,您可能没有到达 chain 的末端,所以所有用户不需要处理相同的场景。位于链开始的用户可以更早地处理请求。

  8. 责任链模式和装饰者模式有什么区别?

    它们一点也不相同,但你可能会觉得它们在结构上很相似。与前面的区别类似,在责任链模式中,只有一个类处理请求,但是在装饰器模式中,所有的类都处理请求。您必须记住,装饰器只在添加和删除责任的上下文中有效,如果您可以将装饰器模式与单一责任原则结合起来,您就可以在运行时添加/删除单一责任。

  9. 调解人模式和观察者模式有什么区别?

    GoF 说,“这些是竞争模式。它们之间的区别在于,观察者通过引入观察者和主体对象来分发通信,而中介对象封装了其他对象之间的通信。”我建议你考虑一下第二十一章中的中介模式例子。在这个例子中,两个工人总是从他们的老板那里得到信息。他们是否喜欢这些信息并不重要。但是如果他们只是简单的观察者,那么他们应该可以取消他们老板对他们的控制,有效地说“我不想看到来自老板/Raghu 的消息。”

    GoF 还发现,与制作可重用的中介相比,当你制作可重用的观察者和主体时,你可能会面临更少的挑战。但是关于交流的流程,中介模式比观察者模式得分更高。

  10. 单例类和静态类,你更喜欢哪一个?

首先要记住的是,Java 不支持顶级静态类。您可以创建单例类的对象,这在静态类中是不可能的。因此,继承和多态的概念可以用单例类来实现。现在让我们考虑一种支持全阶段静态类的语言(比如 C#)。在这种情况下,一些开发人员认为在现实世界的应用程序中模仿静态类(例如,考虑单元测试场景)是具有挑战性的。
  1. 如何区分代理和适配器?
代理在与它们的主体相似的接口上工作,但是适配器在不同的接口上工作(对于它们所适应的对象)。
  1. 代理和装饰者有什么不同?
有不同类型的代理,它们因实现而异。因此,看起来有些实现接近装饰者。例如,保护代理可以像装饰器一样实现。但是你必须记住,装饰者专注于增加责任,而代理者专注于控制对对象的访问。
  1. 中介和门面有什么不同?
总的来说,两者都简化了复杂的系统。在中介模式中,中介和内部子系统之间存在双向连接,而在外观模式中,您提供单向连接(子系统不知道外观)。
  1. 享元模式和状态模式之间有什么联系吗?
GoF 说 flyweight 模式可以帮助你决定*何时以及如何*共享状态对象。
  1. 简单工厂、工厂方法和抽象工厂设计模式之间有什么相似之处?
它们都封装了对象创建。他们建议你编写抽象(接口)代码,而不是具体的类。这些工厂中的每一个都通过减少对具体类的依赖来促进松散耦合。
  1. 简单工厂、工厂方法和抽象工厂设计模式有什么区别?
这是你在各种工作面试中可能会面临的一个重要问题。建议你看清楚。所以,参考第五章“问答环节”第 3 题的回答。
  1. 如何区分单例模式和工厂方法模式?
单例模式确保您每次都能获得一个唯一的实例。它还限制创建其他实例。
但是工厂方法模式并没有说你只能得到一个唯一的实例。大多数情况下,这种模式用于创建任意多的实例,并且这些实例不一定是唯一的。这些新类型化的实例可以实现一个公共基类。(还记得*工厂方法让一个类将实例化推迟到 GoF 定义中的子类*吗?)

18. 如何区分构建者模式和原型模式?

在原型模式中,您使用的是克隆/复制机制。因此,在最后,您可能想要覆盖最初的实现(注意在我们的 Ford 类和 Nano 类的实现中的单词 *@override* )。但是改变遗留(或原始)代码并不总是容易的。

除了这一点,当您使用克隆机制时,您不需要考虑具有不同参数的构造函数。

但是在构建器模式实现中,使用带有不同参数的构造器是很常见的。

19. 如何区分访问者模式和策略模式?

在策略模式中,每个子类使用不同的算法来解决一个共同的问题。但是在访问者设计模式中,每个访问者子类可能提供不同的功能。

20. 空对象和代理有什么不同?

一般来说,代理在某些时候作用于真实对象,它们也可以提供行为。但是空对象不做任何这样的操作。

21. 如何区分解释者模式和访问者模式?

在解释器模式中,您将简单的语法表示为对象结构,但是在访问者模式中,您定义了想要在对象结构上使用的特定操作。除此之外,解释器可以直接访问所需的属性,但是在访问者模式中,您需要特殊的功能(类似于观察者)来访问它们。

22. 如何区分 flyweight 模式和对象池模式?

我没有在本书中讨论对象池模式。但是如果你已经了解了对象池模式,你会注意到在 flyweight 模式中,flyweight 有内部和外部状态。因此,如果一个 flyweight 有两种状态,那么这两种状态是分开的,客户端需要将状态的一部分传递给它。此外,一般而言,客户端不会更改固有状态,因为它是共享的。

对象池模式不存储外部状态的任何部分;所有状态信息都存储/封装在池化对象中。此外,客户端可以更改池化对象的状态。

23. 库(或框架)与设计模式有什么相似/不同之处?

它们不是设计模式。它们提供了可以直接在应用程序中使用的实现。但是他们可以在那些实现中使用模式的概念。

第一部分:四种模式

第二部分:附加设计模式

第三部分:关于设计模式的最后讨论