要避免的设计模式

时间:2009-01-16 07:07:07

标签: design-patterns anti-patterns

许多人似乎都同意,Singleton模式有许多缺点,有些甚至建议完全避免这种模式。有一个excellent discussion here。请将关于Singleton模式的任何评论指向该问题。

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

12 个答案:

答案 0 :(得分:145)

模式很复杂

应谨慎使用所有设计模式。在我看来you should refactor towards patterns当有正当理由这样做而不是立即实施模式时。使用模式的一般问题是它们增加了复杂性。过度使用模式会使特定的应用程序或系统难以进一步开发和维护。

大多数情况下,有一个简单的解决方案,您不需要应用任何特定模式。一个好的经验法则是每当代码片段被替换或需要经常更改时使用模式,并准备好在使用模式时对复杂代码进行警告。

如果您发现支持代码更改的实际需要,请记住your goal should be simplicity并使用模式。

模式原则

如果它们显然会导致过度设计和复杂的解决方案,那么使用模式似乎没有实际意义。然而,对于程序员来说,阅读设计技术和原理为大多数模式奠定基础更为有趣。实际上我的一个favorite books on 'design patterns' stresses this重申了哪些原则适用于所讨论的模式。它们很简单,在相关性方面比模式更有用。只要您可以构建代码模块,有些原则通常足以涵盖面向对象编程(OOP),例如Liskov Substitution Principle

有许多设计原则,但first chapter of GoF book中描述的原则在开始时非常有用。

  • 编程为“界面”,而非“实施”。(Gang of Four 1995:18)
  • 赞成'对象构成'而非'阶级继承'。(Gang of Four 1995:20)

让那些人沉浸在你身上一段时间。应该注意的是,当写入GoF时,interface表示任何抽象(也意味着超类),不要将接口与Java或C#中的类型混淆。第二个原则来自观察到的过度使用继承,即sadly still common today

从那里你可以阅读罗伯特塞西尔马丁SOLID principles所知的(aka. Uncle Bob)。 Scott Hanselman在podcast about these principles中采访了鲍勃叔叔:

  • S ingle责任原则
  • O 笔封闭原则
  • L iskov替代原则
  • nterface隔离原则
  • D 依赖性倒置原则

这些原则是您与同行一起阅读和讨论的良好开端。您可能会发现这些原则相互交织,并与separation of concernsdependency injection等其他流程交织在一起。在执行TDD一段时间后,您也可能会发现这些原则在实践中很自然,因为您需要在某种程度上遵循这些原则才能创建隔离可重复单元测试。

答案 1 :(得分:21)

设计模式的作者最担心的是“访客”模式。

这是一种“必要的邪恶” - 但经常被过度使用,对它的需求经常会在你的设计中揭示出一个更为根本的缺陷。

“访问者”模式的另一个名称是“多调度”,因为当您希望使用单一类型调度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);
    ...
}

当然,这取决于 - 有时你确实需要编写代码来处理所有这些不同的情况 - 但是在采取倾向和使用访问者之前,值得暂停和思考。它可能会在以后为你节省很多痛苦。

答案 2 :(得分:16)

单身人士 - 一个使用单身人士X的课程依赖于它,很难看到并难以隔离进行测试。

它们经常被使用,因为它们方便易懂,但它们确实使测试复杂化。

请参阅Singletons are Pathological Liars

答案 3 :(得分:14)

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

  • 很多时候,由于“错误的原因”,它会占用您的继承层次结构。
  • 基类会有各种各样无关的代码。
  • 它会强制您在开发过程中很早就锁定设计。 (在很多情况下过早锁定)
  • 在稍后阶段改变这一点会变得越来越难。

答案 4 :(得分:9)

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

如果我们从一开始就定义我们想要使用给定的DP,我们的许多未来设计决策将受到该选择的影响,并不能保证我们选择的DP适合我们的需求。

我们也不应该做的一件事是将DP视为不可变的实体,我们应该根据我们的需要调整模式。

所以,sumarizing,我认为我们不应该避免使用DP,我们应该在它们已经在我们的架构中形成时接受它们。

答案 5 :(得分:7)

我认为Active Record是一种过度使用的模式,它鼓励将业务逻辑与持久性代码混合在一起。它不能很好地从模型层隐藏存储实现并将模型绑定到数据库。有许多替代方案(在PoEAA中描述),例如表数据网关,行数据网关和数据映射器,它们通常提供更好的解决方案,当然有助于提供更好的存储抽象。此外,您的模型不应需要存储在数据库中;如何将它们存储为XML或使用Web服务访问它们呢?改变模型的存储机制有多容易?

也就是说,Active Record并不总是坏的,对于其他选项过度的简单应用来说是完美的。

答案 6 :(得分:6)

很简单......避免设计模式不清楚给您或您感觉不舒服

说出一些......

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

  • Interpreter
  • Flyweight

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

  • Abstract Factory - 带有已创建对象系列的完整抽象工厂模式并不像现在这样轻而易举
  • Bridge - 如果抽象和实现被划分为子树,可能会过于抽象,但在某些情况下是非常有用的模式
  • Visitor - 双重调度机制理解真的是必须的

并且有些模式看起来非常简单,但不是那么明确的选择,因为与其原则或实现相关的各种原因:

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

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

对于“难以掌握”的人来说......当他们在合适的地方使用并且实施得很好时,他们真的很有帮助......但是如果使用不当,他们就是噩梦。

现在,下一步是什么......

答案 7 :(得分:5)

我希望我不会为此受到太多打击。 Christer Ericsson在他的one中撰写了两篇关于设计模式主题的文章(tworeal time collision detection blog)。他的语气相当苛刻,也许有点挑衅,但男人知道他的东西,所以我不会把它当成疯子的狂欢。

答案 8 :(得分:5)

有人说service locator是一种反模式。

答案 9 :(得分:2)

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

答案 10 :(得分:2)

Spoike的帖子的补充,Refactoring to Patterns是一个很好的阅读。

答案 11 :(得分:0)

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

替代方案是:

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

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