误用的设计模式
Posted
技术标签:
【中文标题】误用的设计模式【英文标题】:Misused design patterns 【发布时间】:2010-09-20 02:07:56 【问题描述】:在典型的四人帮列表中,您是否经常发现任何被滥用、误解或过度使用的设计模式(除了备受争议的单例模式)?换句话说,您是否建议在使用之前三思而后行的设计模式? (为什么?)
【问题讨论】:
我认为您不应该阻止使用它们中的任何一个(如果它们适合这种情况)。这些模式旨在解决常见问题,您需要在告知不鼓励使用解决问题的模式之前告知那些实施它们的问题。 【参考方案1】:工厂模式...
我被空降到一个项目之前,系统中的每个MyObject
都有一个等效的MyObjectFactory
用于生成新实例。没有抽象类或扩展类的概念……只有普通的 ClassX 和 ClassXFactory。
没有人能解释为什么......“这就是事情一直以来的做法”
【讨论】:
有时必须这样做以便于测试不良代码。使用工厂,您的测试代码可以模拟任何工厂并用自己的工厂替换它们——但如果您打算这样做,为什么不使用 DI? 没错。静态链接到工厂并不比通过 new 运算符静态链接好多少(除非至少有一些对象重用的可能性)。【参考方案2】:单例模式.. 全局状态在测试时经常会导致问题
任何依赖于单例的代码都变得越来越难测试,因为这种依赖不容易被模拟..
【讨论】:
在进行并发或必须完全重置应用程序状态的应用程序(例如游戏)时,不仅测试它们也会出现问题。 每个人总是讨厌单例模式。我很惊讶这不是最重要的。 我经常问想要使用单例的人是否考虑只使用静态类。如果他们说不,我会问他们使用单例实际上有什么不同。单例应该是关于实例化控制,而不是全局访问。 在静态类上使用单例的一个原因是当你需要一个真实的对象,而不仅仅是一堆函数。单例对象可以从另一个类派生或实现一个接口。在最常用的语言中,静态类不能。当然,这并不能解决 Singleton 的问题,在使用它之前你真的应该三思而后行。 FWIW,我只在完全无状态的情况下使用单例。当然,您可以实例化多个这些对象,但它们都会做同样的事情。【参考方案3】:唯一的一个(除了前面提到的 Singleton 和它的犯罪伙伴,工厂)不会是 GoF,当应用于对象的原生属性时,它将是 setter 和 getter。
应用于成员变量的 Setter 和 getter 在功能上与公共成员变量相同。没有 setter 的 getter 更像是 public final 成员变量——但在这一点上,为什么不直接使用 public final 成员变量,它们没有更多的伤害......
唯一的区别是您“可以”拦截呼叫并覆盖它,但人们很少这样做。更多时候,它被用作过程程序员避免 OO 编程的拐杖(这是它成为反模式的真正原因)。
使用 setter 和/或 getter,您仍将内部成员结构暴露给外部世界(例如,如果您发现需要将 int 更改为 long,则必须重构其他类)并且您是几乎可以确保应该在对象内部的一些代码被放置在外部。
我能想到几个例外:
用于简化对象构造的设置器。有时需要先创建一个对象,然后再设置其他值。为了安全起见,这些值应该是不可变的(您不能两次调用 set)。
Getter 用于访问包含的对象。由于包含的对象通常能够确保自己的完整性,因此共享它们非常好。在这种情况下,二传手通常很糟糕,您不希望在您的鼻子下方交换具有特定状态的对象,这使得确保您自己的完整性变得更加困难。
用于屏幕组件的 Java Bean:是的,想不出更好的方法来实现这些“属性球”。反射对于这个组件很方便,模式很有用——它有点老套,但很有效。
DAO/DTO Bean 对象。老实说,我认为这些是模式的不确定用法,但它们是 the 模式。它使得通过元数据而不是代码来操作属性比它应该的要困难得多,因为它必须是反射的。 bean 属性总是与某些外部源(数据库格式、数据传输格式、组件属性……)相关联,那么为什么我们要重复定义每个部分的工作呢?
编辑:从 kyoryu 的评论中被盗,被带到帖子上,因为它真的是我所说的完美总结,可能会在 cmets 中被遗漏。需要,因为似乎不是每个人都明白向语言添加访问器只会编写糟糕的 OO 设计模式:
短版 -
if (account1.balance > 1000)
account1.balance = account1.balance - 1000;
account2.balance = account2.balance + 1000;
; = BAD CODE.
account2.deposit(account1.withdraw(1000)); = GOOD CODE.
第二个不需要访问器... – kyoryu (比尔 k 稍作修改,因为我的空间比他在评论中所做的多一点)。
第二个是在 Account 中移动测试和其他一些数学运算,而不是在您可能进行转移的每个地方都在整个代码中复制它。
为了更详细地说明这一点,请注意,对于“GOOD CODE”样式,很明显 .withdraw 的输出可能是一个 Transaction 对象,其中包含有关整个事务的信息,包括其成功、来源和目的地以及日志记录能力。以“BAD CODE”风格编写代码的人怎么会发生这种情况?
另外,您将如何重构 BAD CODE 以使用这样的对象?只是一团糟。
【讨论】:
Getter 和 setter 与许多其他模式一样,都是为了纠正语言的限制。在这种情况下,属性。当看到其他语言的公共变量时,许多 Java 程序员都会害怕得尖叫起来。他们经常忘记在 saner 语言中,如果您需要为该变量的 set 或 get 添加逻辑,您可以将其替换为同名的属性,而不是制作很多 @ 987654322@ 和setId()
以防万一。 (甚至 VB6 也有属性)
实际上,属性与 getter 和 setter 一样是问题所在。 OO 的想法是您要求一个对象为您做某事。 getter 或 setter 在哪里适合? Getter 或只读属性并不是真正有害的,有时需要(避免使用它们,除非你需要它们),但 setter/可写属性很糟糕。我对属性的最大问题是它们鼓励这种不良行为。当你绝对需要它们时,它们是一种更好的语法,但在语言中内置属性会鼓励人们认为它们是个好主意。
正是比尔 K 所说的。短版 - if (account.balance > 1000)account.balance = account.balance - 1000;TransfertoMe(1000); = 错误代码。 account.Withdraw(1000); = 好代码。第二个不需要访问器...【参考方案4】:
我还要说工厂模式。与 Eoin 类似的经历。在我的例子中,这个项目有很多工厂,因为有些人认为你可能使用对象 A 来实现本地实现,对象 B 来实现远程实现,并且它是通过工厂抽象出来的(这是一个明智的做法)。
但是“远程”实现从未被需要或实现,甚至在未来从未被预见过......而且,技能较低的工程师开始在许多其他事情上采用这种模式,就像千篇一律......
【讨论】:
“YAGNI”的经典案例:你不需要它!【参考方案5】:实际上,我最常看到的是缺乏使用适当的模式。典型场景:我:“嘿,模块A已经有一段代码循环一组对象并对它们执行数据库操作X,你为什么不重用那段代码?”编码员:“好吧,但我必须对那些对象进行 Y 操作。”我:“如何使用重构它以使用命令模式来执行 X 或 Y?”
我曾经看到 Subject-Observer 模式的使用失控。它是在使用数据库持久存储主题的进程之间实现的。由于该主题的更新数量和观察者的数量众多,数据库的负载非常巨大,并导致了无法预料的系统范围内的减速。
【讨论】:
【参考方案6】:如果各种逻辑都被归为一个庞大的类,那么中介者模式肯定有可能被滥用。
【讨论】:
【参考方案7】:实际上,当只需要一个 KISS(保持简单、愚蠢保持简短)解决方案时,我会说设计模式通常被过度使用。
设计模式非常适合使系统灵活,但代价是使实现更加复杂。只有当灵活性提供实际优势时,这才是值得的权衡。
【讨论】:
在这种情况下,我猜 KISS 也可以代表“Keep It Stupid Stupid” (平心而论,根据我的经验,这大约是 90% 的时间)【参考方案8】:全部。
在这里不要误会我的意思,我发现它们是一个很好的基础,如果理解得当,非常有帮助。掌握设计技能以了解何时以及如何在代码中应用它们需要付出很多努力。实际上,这就是创建干净代码的技能的总体情况。
这不是关于不使用的问题,这正是您在“使用前三思而后行”的问题中所说的。很好地理解你在做什么以及为什么。如果您不这样做,请与可以指导您的人交谈-除了大量阅读之外。当心那些知道所有模式但无法清楚地向您解释其中任何一个的原因/原因的人 - 特别是有问题的那个。
【讨论】:
对我来说,最重要的是,如果您从与“传统”伪过程 OO 稍有不同的编程模型来看待设计模式,那么这些设计模式最有用。使用设计模式编写的代码应该看起来非常像使用 Actor 模型的代码 - 如果使用类似 Actor 的模型编写代码,则设计模式变得非常实用且经常显而易见。 +1 这就是引入 AntiPatterns 的原因。在错误的上下文中使用的设计模式会变成反模式,这意味着弊大于利。【参考方案9】:我看到的最大的一个是单例模式,在该模式中,对于如何以及何时调用单例的析构函数没有足够的关注和勤奋。
对于这种普遍存在的模式,几乎没有任何关于决定何时必须死亡的正确过程的讨论。
只是我的 0.02。
干杯,
罗伯
【讨论】:
【参考方案10】:在看到一些“所有模式都是错误的”答案后,我只想添加另一条评论。
如果您是一个半正派的程序员,正在处理具有中等挑战性的问题,那么几乎所有的模式都应该在某一时刻向您展示自己作为问题的“明显”解决方案。
《设计模式》这本书唯一真正的意义是为我们每天所做的事情命名,这样我们就可以更好地沟通。
我想如果你是一个新手程序员,通读一遍它们会很有帮助,这样在你需要的那一天你就不必自己弄清楚了,它已经在你的工具箱里了,但总的来说——其中任何一个都可以由一帮人(任何一个!)来解决。
如果您还不知道其中的任何一个,那么您可能永远不需要它。
将名称命名为模式并稍微正式化它们非常有用,我一点也不抱怨这本书。如果你没有看到这里几乎所有模式的偶尔需要,你只是没有在编写非常难的代码(或者你重复代码很多,我认为这是大罪)。
【讨论】:
当然你可以自己弄清楚它们,但要弄清楚所有的细微之处需要一些时间,当你遇到类似的问题时,你可能不得不再次经历这个过程。但我普遍同意,这一切背后没有魔法。如果您是一名优秀的程序员,并且对 OOD 有一定的了解,那么您可能会在不知不觉中使用设计模式。【参考方案11】:观察者模式在 C# 中非常没用,因为它有事件。
【讨论】:
C# 事件是观察者模式的一种实现。 我不知道 C# 中的事件,但“是模式”和“不是模式”之间的区别肯定取决于语言。在迭代器上思考...... 我想即使是“例行调用”也可以被认为是一种“设计模式”,早在 1940 年代... 我认为他的意思是,由于 c# 事件是观察者模式的实现,所以没有必要“自己动手”。 这个问题特别提到了 GoF 书中的设计模式,我认为这意味着按照书中的描述实现它们。如果您要实现书中描述的观察者,那将是多余的,因为 C# 有事件。我并不是说你不需要在 C# 中观察对象,因为那太荒谬了。我也坚持我的立场,即设计模式与语言功能不同,两者不应混淆。【参考方案12】:首先,“这取决于”语言 - 某些语言中的某些结构减少了对某些设计模式的需求。
其次,Design Pattern 概念模板的一部分从一开始就包含“适用性”和“后果”部分 - 忽略这些部分后果自负。 “了解”一个模式不仅意味着您知道如何用您选择的语言对其进行编码 - 它还意味着知道何时使用它,以及使用它可能带来的缺点。
【讨论】:
【参考方案13】:仅在需要时使用模式。您无法预测未来,因此虽然您可能会加入一个模式以使设计灵活,但当产品采用不同的方向并且您的模式成为阻碍您实现用户想要的东西时会发生什么?
让设计从一开始就尽可能简单。当您更多地了解您的设计需要如何更改时,请使用适当的模式,而不是之前。
【讨论】:
【参考方案14】:存储库模式
大多数人在阅读 Eric Evans 的《领域驱动设计》一书后就开始使用这种模式。
这里有多少人见过像数据访问对象一样构建的存储库?
【讨论】:
【参考方案15】:您无法直接回答这个问题。它主要是主观的,取决于应用程序的要求。
大多数人引用Singleton_pattern 不好,但对每个用户和项目来说都不错。对于我的项目要求,它可以达到目的。我需要一个 ConnectionManager 来处理客户端和应用程序之间的会话管理,而 Singleton 可以出色地完成这项工作。
您提供了一个具有良好文档的第三方 jar。 jar 包含继承层次结构。现在您必须对每个孩子添加一个操作。由于您没有源代码,因此您无法做到。现在您可以通过使用Visitor_pattern 受益。但是如果你有源代码,你可能根本不会使用Visitor
模式。您可以在父级和每个子级中简单地添加新操作。缺少源代码并不意味着我在滥用Visitor
模式。
我想限制各种对象之间的通信。我将继续使用实现Mediator_pattern 进行对象通信。我想限制客户接触我的系统以隐藏复杂性。我将继续使用Facade_pattern。这并不意味着我在滥用这些模式。同样的示例可以扩展到其他模式,例如 Proxy_pattern 等。
【讨论】:
以上是关于误用的设计模式的主要内容,如果未能解决你的问题,请参考以下文章