设计模式要避免[关闭]


105

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

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


我只需要注意关于Design Antipatterns的
开发者

@casperOne为什么您要关闭问题?这个问题是合理的。
bleepzter

Answers:


149

模式很复杂

所有设计模式都应谨慎使用。我认为您应该在有正当理由的情况下重构模式,而不是立即实施模式。使用模式的一般问题是它们会增加复杂性。模式的过度使用使给定的应用程序或系统繁琐,无法进一步开发和维护。

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

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

模式原则

如果使用模式显然会导致过度设计和复杂的解决方案,那么使用模式似乎似乎毫无意义。但是,对于程序员而言,阅读构成大多数模式基础的设计技术和原理会更加有趣。实际上,我最喜欢的一本关于“设计模式”的书通过重申适用于该模式的原理强调了这一点。就相关性而言,它们足够简单,可以比模式有用。只要您可以构建代码模块,其中的某些原则就足以涵盖除面向对象编程(OOP)以外的其他内容,例如Liskov Substitution Principle

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

  • 编程为“接口”,而不是“实现”。(四人帮1995:18)
  • 赞成“对象组成”胜于“类继承”。(四人帮1995:20)

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

从那里您可以阅读Robert Cecil Martin (又名Bob叔叔)所熟知的SOLID原理。斯科特·汉塞尔曼(Scott Hanselman)在播客中采访了鲍勃叔叔,涉及这些原则

  • 小号英格尔责任原则
  • O封闭原则
  • 大号 iskov替换原则
  • 覆盖整个院落分离原则
  • d ependency倒置原则

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


7
+1很好的答案。似乎今天每个(新手)程序员都知道他/她的设计模式,或者至少知道它们的存在。但是很多人从未听说过,更不用说适用某些绝对必要的原则,例如“单一责任”来管理其代码的复杂性。
eljenso

21

Design Patterns的作者自己最担心的就是“ Visitor”模式。

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

“访问者”模式的另一个名称是“多调度”,因为当您希望使用单一类型的调度OO语言来基于两种类型选择要使用的代码时,访问者模式就是最终的结果(或更多)不同的对象。

典型的例子是您在两个形状之间有交集,但是有一个甚至更容易被忽略的情况:比较两个异构对象的相等性。

无论如何,通常您最终会得到如下结果:

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

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

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

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

interface IShape
{
    Area getArea();
}

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

当然,这取决于-有时您确实确实需要编写代码来处理所有这些不同的情况-但值得一试,在投入使用和使用Visitor之前先三思而后行。以后可能会减轻您的很多痛苦。


2
谈到访客,鲍勃叔叔一直“使用” butunclebob.com/ArticleS.UncleBob.IuseVisitor
Spoike,

3
@Paul Hollingsworth您能否提供参考说明设计模式作者感到担心的地方(以及他们为什么担心)?
m3th0dman 2013年

16

单例-使用单例X的类对此具有依赖性,这很难看到也很难隔离以进行测试。

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

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


1
他们还可以简单地进行测试,因为它们可以为您注入一个Mock对象。一切都取决于平衡。
马丁·布朗

1
@Martin:如果可以的话,可以更改一个singelton进行测试(如果您不使用标准的singelton实现),那比在构造函数中传递测试实现更容易呢?
orip

14

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

  • 很多时候,由于“错误的原因”,它用尽了继承层次结构。
  • 基类容易被各种无关的代码所困扰。
  • 它迫使您通常在开发过程的早期就锁定设计。(在许多情况下过早锁定)
  • 在以后的阶段对此进行更改变得越来越困难。

2
我要补充一点,每当您使用模板方法时,您最好使用策略。TemplateMethod的问题在于,基类和派生类之间存在可重入性,而后者通常耦合过度。
Paul Hollingsworth,2009年

5
@Paul:正确使用模板方法非常有用,例如,当变化的部分需要对不知道的部分有很多了解时。我的看法是,当基本代码仅调用自定义代码时应使用策略,而当自定义代码固有地需要了解基本代码时应使用模板方法。
dsimcha 2010年

是的,dsimcha,我同意……只要班级设计师意识到这一点。
Paul Hollingsworth'2

9

我认为您不应该避免使用设计模式(DP),也不要在计划体系结构时强迫自己使用DP。仅当DP自然从我们的计划中出现时,我们才应使用它们。

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

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

因此,总而言之,我不认为我们应该避免使用DP,而当DP已经在我们的体系结构中成型时,我们应该拥抱它们。


7

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

也就是说,Active Record并不总是坏的,它非常适合其他选项过于简单的简单应用程序。


1
Kinda是正确的,但在某种程度上取决于实施。
Mike Woodhouse

6

这是简单...避免设计模式都不清楚你或那些你不觉得舒服

列举一些...

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

  • Interpreter
  • Flyweight

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

  • Abstract Factory -带有创建对象族的完全抽象工厂模式似乎并不那么容易
  • Bridge -如果将抽象和实现划分为子树,可能会变得过于抽象,但在某些情况下是非常有用的模式
  • Visitor -了解双重派遣机制确实是必须的

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

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

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

对于“较难掌握”的人……当在适当的地方使用它们以及实施得当时,它们确实是非常有用的帮助……但是,如果使用不当,它们将成为噩梦。

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


当您多次使用资源(通常是图像)时,必须使用轻量模式。这不是模式,而是解决方案。
Cengiz Kandemir

5

我希望我不会为此受到太大伤害。克里斯特•爱立信(Christer Ericsson )在他的实时碰撞检测博客中写了两篇关于设计模式的文章()。他的语气相当刺耳,也许有点挑衅,但是这个人知道他的东西,所以我不会把它当作疯子的狂欢。


有趣的读物。感谢您的链接!
孟买

3
白痴产生错误的代码。与没有样式的白痴相比,具有样式的白痴会产生更差的代码吗?我认为他们没有。对于聪明的人,模式提供了众所周知的词汇,从而简化了思想交流。解决方案:学习模式,只与聪明的程序员打交道。
马丁·布朗

我认为,一个真正的傻瓜真的不可能产生更糟糕的代码-无论他们使用什么工具
1800 INFORMATION

1
我认为他的大学考试示例仅证明,那些鄙视自己的问题领域并且不愿意在一个周末内学习超过几个小时的人在尝试解决问题时会得出错误的答案。
scriptocalypse

5

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


同样重要的是要注意有时需要服务定位器。例如,当您没有适当控制对象的实例化时(例如,C#中具有非恒定参数的属性)。但是也可以将服务定位器与ctor注入一起使用。
Sinaesthetic's

2

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


4
我知道您回答中的所有内容,直到单词“ A”
1800信息

您可能需要扩展或链接到您所讨论的此自动依赖性分析。同样在.NET中,使用委托/事件代替观察者模式。
Spoike

3
@Spoike:代表/事件是观察者模式的实现
Orip

1
我个人对Observer的怨恨是,它可能以垃圾收集的语言创建内存泄漏。完成对象处理后,您需要记住该对象不会被清理。
马丁·布朗2009年

@orip:是的,这就是为什么要使用委托/事件的原因。;)
Spoike


0

迭代器是另外一种可以避免使用的GoF模式,或者至少在没有其他选择可用时才使用它。

替代方法是:

  1. 每个循环。大多数主流语言中都存在此构造,并且在大多数情况下可以避免使用迭代器。

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


我同意选择器。Foreach是一个迭代器,大多数OO语言都提供了一个可迭代的接口,您可以通过实现该接口来实现foreach。
尼尔·艾特肯

在某些语言中,可以通过迭代器实现for-each构造,但实际上它的概念更高级,更接近选择器。在使用for-each时,开发人员明确声明应处理容器中的所有元素。
Volodymyr Frolov

迭代器是一个很好的模式。反模式将实现没有迭代器的IEnumerable / IEnumerator 。我相信LINQ是通过yield迭代器实现的。埃里克·怀特(Eric White)在C#3.0中对此进行了一些精彩的讨论:blogs.msdn.com/b/ericwhite/archive/2006/10/04/…。另外,请查看Jeremy Likness关于带有迭代器的协程的讨论:wintellect.com/CS/blogs/jlikness/archive/2010/03/23/…

@Ryan Riley,迭代器是低级对象,因此在高级设计和代码中应避免使用它们。迭代器和各种选择器的实现细节在这里无关紧要。选择器与迭代器不同,它使程序员可以明确表达要处理的内容,从而使它们具有较高的层次。
Volodymyr Frolov

Fwiw,类似于LINQ的F#语法是`List.map(fun x-> x.Value)xs`,它与列表理解的时间差不多。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.