110

很多人似乎都同意,单例模式有许多缺点,有些人甚至建议完全避免这种模式。这里有一个很好的讨论。请将有关单例模式的任何评论指向该问题。

我的问题:是否有其他设计模式应该避免或小心使用?

4

12 回答 12

152

模式很复杂

应谨慎使用所有设计模式。在我看来,当有正当理由时,您应该重构模式,而不是立即实施模式。使用模式的一般问题是它们增加了复杂性。过度使用模式会使给定的应用程序或系统难以进一步开发和维护。

大多数时候,有一个简单的解决方案,您不需要应用任何特定的模式。一个好的经验法则是在代码片段倾向于被替换或需要经常更改时使用模式,并准备在使用模式时承担复杂代码的警告。

请记住,如果您发现支持代码更改的实际需求,您的目标应该是简单性并采用一种模式。

模式之上的原则

如果模式明显会导致过度设计和复杂的解决方案,那么使用模式似乎没有意义。然而,对于程序员来说,阅读为大多数模式奠定基础的设计技术和原则会更有趣。事实上,我最喜欢的一本关于“设计模式”的书通过重申哪些原则适用于所讨论的模式来强​​调这一点。它们足够简单,在相关性方面比模式有用。只要您可以构建代码的模块,其中一些原则就足以涵盖面向对象编程 (OOP) 之外的更多内容,例如Liskov 替换原则。

有许多设计原则,但GoF 书第一章中描述的那些在开始时非常有用。

  • 编程到“接口”,而不是“实现”。(四人帮 1995:18)
  • 偏爱“对象组合”而不是“类继承”。(四人帮 1995:20)

让那些沉没在你身上一段时间。应该注意的是,在编写 GoF 时,接口意味着任何抽象(也意味着超类),不要与作为 Java 或 C# 中的类型的接口混淆。第二个原则来自观察到的过度使用继承,遗憾的是今天仍然很普遍

从那里您可以阅读由罗伯特·塞西尔·马丁(又名鲍勃叔叔)提出的SOLID 原则Scott Hanselman 在播客中就这些原则采访了 Bob 叔叔:

  • 单一责任原则
  • 闭原则
  • 利斯科夫替换原则
  • 接口隔离原则
  • 依赖倒置原则

这些原则是阅读和与同行讨论的良好开端。您可能会发现这些原则相互交织,并与其他过程(例如关注点分离依赖注入)交织在一起。在进行了一段时间的TDD之后,您可能还会发现这些原则在实践中很自然,因为您需要在一定程度上遵循它们才能创建独立可重复的单元测试。

于 2009-01-16T07:22:53.810 回答
23

设计模式的作者们自己最担心的是“访问者”模式。

这是一个“必要的邪恶”——但经常被过度使用,并且对它的需求往往揭示了你设计中更根本的缺陷。

“Visitor”模式的另一个名称是“Multi-dispatch”,因为当您希望使用单一类型的调度 OO 语言根据两种类型选择要使用的代码时,您最终会使用访问者模式(或更多)不同的对象。

典型的例子是你有两个形状之间的交集,但还有一个更简单的情况经常被忽视:比较两个异构对象的相等性。

无论如何,通常你会得到这样的结果:

interface IShape
{
    double intersectWith(Triangle t);
    double intersectWith(Rectangle r);
    double intersectWith(Circle c);
}

这样做的问题是您已经将“IShape”的所有实现耦合在一起。您已经暗示,每当您希望向层次结构添加新形状时,您也需要更改所有其他“形状”实现。

有时,这是正确的最小设计 - 但请仔细考虑。您的设计是否真的要求您需要分派两种类型?你愿意写出多方法的组合爆炸吗?

通常,通过引入另一个概念,您可以减少实际必须编写的组合数量:

interface IShape
{
    Area getArea();
}

class Area
{
    public double intersectWith(Area otherArea);
    ...
}

当然,这取决于 - 有时您确实需要编写代码来处理所有这些不同的情况 - 但在尝试使用 Visitor 之前,值得暂停一下并考虑一下。它可能会在以后为您节省很多痛苦。

于 2009-01-16T10:02:10.133 回答
16

单例 - 使用单例 X 的类对它有一个难以看到且难以隔离以进行测试的依赖。

它们经常使用,因为它们方便且易于理解,但它们确实会使测试复杂化。

请参阅单身人士是病态的骗子

于 2009-01-16T08:16:11.953 回答
14

我相信模板方法模式通常是一种非常危险的模式。

  • 很多时候,它会因为“错误的原因”耗尽你的继承层次结构。
  • 基类倾向于散布各种不相关的代码。
  • 它迫使您锁定设计,通常是在开发过程的早期阶段。(在很多情况下过早锁定)
  • 在以后的阶段改变这一点变得越来越难。
于 2009-01-16T07:19:44.393 回答
9

我认为您不应该避免使用设计模式 (DP),并且我认为您不应该在规划架构时强迫自己使用 DP。我们应该只在我们的计划中自然出现时才使用 DP。

如果我们从一开始就定义要使用给定的 DP,那么我们未来的许多设计决策都会受到该选择的影响,无法保证我们选择的 DP 适合我们的需求。

我们也不应该做的一件事是将 DP 视为不可变的实体,我们应该使模式适应我们的需求。

所以,总而言之,我认为我们不应该避免 DP,当它们已经在我们的架构中形成时,我们应该拥抱它们。

于 2009-01-16T09:52:56.860 回答
7

我认为 Active Record 是一种过度使用的模式,它鼓励将业务逻辑与持久性代码混合在一起。它不能很好地从模型层隐藏存储实现并将模型与数据库联系起来。有很多替代方案(在 PoEAA 中描述),例如 Table Data Gateway、Row Data Gateway 和 Data Mapper,它们通常提供更好的解决方案,并且肯定有助于提供更好的存储抽象。此外,您的模型不需要存储在数据库中;将它们存储为 XML 或使用 Web 服务访问它们怎么样?更改模型的存储机制有多容易?

也就是说,Active Record 并不总是很糟糕,并且非常适合其他选项过于繁琐的简单应用程序。

于 2009-01-16T07:20:15.513 回答
6

这很简单...避免使用您不清楚您觉得不舒服的设计模式。

列举一些...

有一些不切实际的模式,例如:

  • Interpreter
  • Flyweight

还有一些更难掌握,例如:

  • Abstract Factory- 具有创建对象系列的完全抽象工厂模式并不像看起来那么容易
  • Bridge- 如果抽象和实现被划分为子树,可能会变得过于抽象,但在某些情况下是非常有用的模式
  • Visitor- 双重调度机制的理解真的是必须的

还有一些模式看起来非常简单,但由于与它们的原理或实现相关的各种原因,并不是那么明确的选择:

  • Singleton- 不是完全糟糕的模式,只是过度使用(经常在那里,不适合的地方)
  • Observer- 很棒的模式......只是让代码更难阅读和调试
  • Prototype- 交易编译器检查动态(这可能是好或坏......取决于)
  • Chain of responsibility- 经常被强行/人为地推入设计中

对于那些“不切实际的”,在使用它们之前应该真正考虑一下,因为通常在某个地方有更优雅的解决方案。

对于“较难掌握”的人......当它们在合适的地方使用并且实施良好时,它们真的是很大的帮助......但是当使用不当时,它们是噩梦。

现在,接下来是什么...

于 2011-04-21T13:38:17.770 回答
5

我希望我不会因此受到太多打击。Christer Ericsson在他的实时碰撞检测博客中写了两篇关于设计模式主题的文章(一篇两篇) 。他的语气相当刺耳,也许还有点挑衅,但这个人知道他的本事,所以我不会认为这是疯子的胡言乱语。

于 2009-01-16T08:26:37.020 回答
5

有人说服务定位器是一种反模式。

于 2010-07-13T12:42:28.773 回答
2

我相信观察者模式有很多要解决的问题,它适用于非常普遍的情况,但是随着系统变得越来越复杂,它变成了一场噩梦,需要 OnBefore()、OnAfter() 通知,并且经常发布异步任务以避免重新入口。一个更好的解决方案是开发一个自动依赖分析系统,在计算期间检测所有对象访问(带有读取障碍),并在依赖图中自动创建一条边。

于 2009-01-16T07:11:07.827 回答
2

对 Spoike 的帖子的补充,重构模式是一本很好的读物。

于 2009-01-16T07:34:58.887 回答
0

迭代器是另一种要避免的 GoF 模式,或者至少只有在没有替代方案可用时才使用它。

替代方案是:

  1. 每个循环。这种结构存在于大多数主流语言中,并且在大多数情况下可用于避免迭代器。

  2. 选择器 à la LINQ 或 jQuery。当 for-each 不合适时应该使用它们,因为并非容器中的所有对象都应该被处理。与迭代器不同,选择器允许在一个地方显示要处理的对象类型。

于 2010-06-01T21:50:29.320 回答