要避免的设计模式

Design patterns to avoid

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

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


型模式是复杂的

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

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

请记住,如果您看到支持代码更改的实际需要,那么您的目标应该是简单性和使用模式。

模式原则

如果模式明显会导致设计过度和复杂的解决方案,那么使用模式似乎是一种无意义的做法。然而,对于程序员来说,读取设计技巧和原则是非常有趣的,这为大多数模式奠定了基础。事实上,我最喜欢的一本关于"设计模式"的书强调了这一点,它重申了什么原则适用于所讨论的模式。它们足够简单,在相关性方面比模式有用。只要您能构建代码的模块,有些原则就足够通用,可以包含比面向对象编程(OOP)更多的内容,比如Liskov替换原则。

有很多设计原则,但是在GOF书的第一章中描述的那些原则从一开始就非常有用。

    百万千克1程序到"接口",而不是"实现"。(四人帮1995:18)百万千克1百万千克1"对象组合"优先于"类继承"。(四人帮1995:20)百万千克1

让那些沉溺在你身上一段时间。应该注意的是,当GoF被写入时,接口意味着任何抽象的东西(这也意味着超级类),而不是与Java或C语言中的接口混淆。第二个原则来自观察到的过度使用继承,可悲的是,这在今天仍然很普遍。

从那里你可以读到罗伯特·塞西尔·马丁(又名罗伯特·塞西尔·马丁)提出的可靠原则。鲍勃叔叔)。斯科特·汉塞尔曼在一个关于这些原则的播客中采访了鲍勃叔叔:

    百万千克1单一责任原则百万千克1百万千克1开闭原理百万千克1百万千克1Liskov替换原则百万千克1百万千克1接口隔离原则百万千克1百万千克1依赖倒置原则百万千克1

这些原则是一个很好的开始去阅读和与你的同龄人讨论。您可能会发现这些原则相互交织在一起,并且与其他过程(如分离关注点和依赖注入)交织在一起。在做了一段时间的TDD之后,您可能会发现这些原则在实践中是自然产生的,因为您需要在某种程度上遵循它们,以便创建隔离的和可重复的单元测试。


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

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

"visitor"模式的另一个名称是"multi-dispatch",因为当您希望使用单一类型的dispatch oo语言根据两个(或更多)不同对象的类型选择要使用的代码时,visitor模式就是您最终要使用的。

经典的例子是两个形状之间有交叉点,但是有一个更简单的例子经常被忽略:比较两个异构对象的相等性。

不管怎样,通常你会得到这样的结果:

1
2
3
4
5
6
interface IShape
{
    double intersectWith(Triangle t);
    double intersectWith(Rectangle r);
    double intersectWith(Circle c);
}

问题在于,您已经将"ishape"的所有实现耦合在一起。您已经暗示,每当您希望向层次结构中添加新形状时,您也需要更改所有其他"形状"实现。

有时,这是正确的最小设计-但仔细想想。你的设计真的要求你分两种类型吗?你愿意写下每一个组合爆炸的多种方法吗?

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

1
2
3
4
5
6
7
8
9
10
interface IShape
{
    Area getArea();
}

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

当然,这要看情况而定——有时候你确实需要编写代码来处理所有这些不同的情况——但是在冒险和使用访问者之前,暂停一下并思考一下是值得的。以后可能会减轻你的痛苦。


singleton s——使用singletonX的类依赖于它,很难看到,也很难为测试隔离。

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

单身汉是病态的骗子。


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

  • 很多时候,它会因为"错误的原因"而耗尽继承层次结构。
  • 基类有一种倾向,会被各种不相关的代码乱丢。
  • 它强制您锁定设计,通常在开发过程的早期。(许多情况下过早锁定)
  • 在以后的阶段改变这一点变得越来越困难。


我不认为你应该避免设计模式(dp),我也不认为你应该强迫自己在规划架构时使用dps。只有当DPS从我们的计划中自然出现时,我们才应该使用它们。

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

我们也不应该做的一件事是把一个dp当作一个不变的实体,我们应该使模式适应我们的需要。

所以,简单地说,我认为我们不应该避免DPS,当它们在我们的体系结构中已经成形时,我们应该拥抱它们。


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

也就是说,活动记录并不总是坏的,对于其他选项可能会被过度破坏的更简单的应用程序来说是完美的。


很简单…避免设计那些你不清楚或者你觉得不舒服的图案。

列举一些…

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

  • Interpreter
  • Flyweight

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

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

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

  • Singleton—不是很糟糕的模式,只是使用过度(通常在不合适的地方)
  • Observer大模式…只是让代码更难读取和调试
  • Prototype交换编译器检查动态(可以是好的也可以是坏的…取决于)
  • Chain of responsibility—通常只是强行/人为地推进设计。

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

对于那些"难以掌握"的人…当它们在适当的地方被使用并且被很好地执行时,它们真的是非常有用的帮助…但如果使用不当,它们就是噩梦。

接下来是什么…

  • 头先设计模式是必须的
  • 来源是"急救"


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


我希望我不会为此被打得太多。ChristerEricsson在他的实时碰撞检测博客中写了两篇关于设计模式的文章(一,二)。他的语气相当刺耳,也许有点挑衅,但这个人知道他的东西,所以我不认为这是疯子的胡言乱语。


作为spoike文章的补充,重构模式是一个很好的读物。


我相信观察者模式有很多需要回答的问题,它在非常一般的情况下工作,但是随着系统变得更加复杂,它变成了一场噩梦,需要onbefore()、onafter()通知,并且经常发布异步任务以避免重新进入。一个更好的解决方案是开发一个自动依赖性分析系统,该系统在计算过程中检测所有对象访问(带有读取屏障),并自动在依赖关系图中创建边。


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

备选方案包括:

  • 对于每个循环。这种结构存在于大多数主流语言中,在大多数情况下可以用来避免迭代器。

  • 选择器_la linq或jquery。当for each不合适时,应该使用它们,因为不应该处理容器中的所有对象。与迭代器不同,选择器允许在一个地方显示要处理的对象的种类。