避免的设计模式[关闭]
Posted
技术标签:
【中文标题】避免的设计模式[关闭]【英文标题】:Design patterns to avoid [closed] 【发布时间】:2010-10-01 18:33:56 【问题描述】:很多人似乎都同意,单例模式有许多缺点,有些人甚至建议完全避免这种模式。有一个excellent discussion here。请将有关单例模式的任何 cmets 指向该问题。
我的问题:是否还有其他设计模式应该避免或谨慎使用?
【问题讨论】:
我只需要注意一些很棒的设计反模式列表deviq.com/antipatterns @casperOne 你为什么关闭这个问题?这个问题是合法的。 【参考方案1】:模式很复杂
应谨慎使用所有设计模式。在我看来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。
从那里您可以阅读由 Robert Cecil Martin (aka. Uncle Bob) 公开的 SOLID principles。 Scott Hanselman 在podcast about these principles 采访了鲍勃叔叔:
单一责任原则 O笔封闭原则 L伊斯科夫替换原则 I接口隔离原则 D依赖倒置原则这些原则是阅读和与同行讨论的良好开端。您可能会发现这些原则相互交织,并与separation of concerns 和dependency injection 等其他流程交织在一起。在做了一段时间TDD 之后,您可能还会发现这些原则在实践中很自然,因为您需要在一定程度上遵循它们才能创建 isolated 和 repeatable 单元测试。
【讨论】:
+1 非常好的答案。今天似乎每个(新手)程序员都知道他/她的设计模式,或者至少知道它们存在。但是很多人从未听说过,更不用说应用了一些绝对必要的原则,例如管理代码复杂性的单一职责。【参考方案2】:设计模式的作者自己最担心的是“访客”模式。
这是一种“必要的邪恶”——但经常被过度使用,而且对它的需求通常会揭示你设计中更根本的缺陷。
“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 之前,值得暂停一下并考虑一下。它可能会在以后为您节省很多痛苦。
【讨论】:
谈到访客,鲍勃叔叔“一直”使用它butunclebob.com/ArticleS.UncleBob.IuseVisitor @Paul Hollingsworth 您能否提供参考,说明设计模式作者在哪里担心(以及他们为什么担心)?【参考方案3】:单例 - 一个使用单例 X 的类对它有一个难以看到且难以隔离以进行测试的依赖项。
它们经常被使用,因为它们方便且易于理解,但它们确实会使测试变得复杂。
见Singletons are Pathological Liars。
【讨论】:
他们也可以简化测试,因为他们可以给你一个单点来注入一个 Mock 对象。这一切都取决于保持平衡。 @Martin:如果当然可以更改一个单点进行测试(如果你不使用标准单点实现),但是这比在构造函数中通过测试实现更容易吗?跨度> 【参考方案4】:我认为模板方法模式通常是一种非常危险的模式。
很多时候它会因为“错误的原因”耗尽您的继承层次结构。 基类倾向于散布各种不相关的代码。 它迫使您锁定设计,通常是在开发过程的早期阶段。 (在很多情况下过早锁定) 在稍后阶段更改此设置变得越来越难。【讨论】:
我要补充一点,每当您使用模板方法时,您可能会更好地使用策略。 TemplateMethod 的问题是基类和派生类之间存在重入,往往过度耦合。 @Paul:模板方法在使用得当时非常棒,即当变化的部分需要了解很多关于不变化的部分时。我的看法是当基础代码只调用自定义代码时应该使用该策略,而当自定义代码本身需要了解基础代码时应该使用模板方法。 是的,dsimcha,我同意......只要类设计者意识到这一点。【参考方案5】:我认为您不应该避免使用设计模式 (DP),而且我认为您不应该在规划架构时强迫自己使用 DP。我们应该只在我们的计划中自然出现时才使用 DP。
如果我们从一开始就定义要使用给定的 DP,我们未来的许多设计决策都会受到该选择的影响,但不能保证我们选择的 DP 适合我们的需求。
我们也不应该做的一件事是将 DP 视为不可变的实体,我们应该使模式适应我们的需求。
所以,总而言之,我认为我们不应该避免 DP,我们应该在它们已经在我们的架构中形成时接受它们。
【讨论】:
【参考方案6】:我认为 Active Record 是一种过度使用的模式,它鼓励将业务逻辑与持久性代码混合在一起。它不能很好地从模型层隐藏存储实现并将模型与数据库联系起来。有很多替代方案(在 PoEAA 中描述),例如 Table Data Gateway、Row Data Gateway 和 Data Mapper,它们通常提供更好的解决方案,并且肯定有助于提供更好的存储抽象。此外,您的模型不应需要存储在数据库中;将它们存储为 XML 或使用 Web 服务访问它们怎么样?更改模型的存储机制有多容易?
也就是说,Active Record 并不总是很糟糕,并且非常适合其他选项过于繁琐的简单应用程序。
【讨论】:
有点正确,但在某种程度上取决于实现。【参考方案7】:这很简单...避免使用不清楚或您不喜欢的设计模式。
举个例子……
有一些不实用的模式,例如:
Interpreter
Flyweight
还有一些较难掌握,例如:
Abstract Factory
- 具有创建对象系列的完整抽象工厂模式并不像看起来那么容易
Bridge
- 如果抽象和实现被划分为子树,可能会变得过于抽象,但在某些情况下是非常有用的模式
Visitor
- 双重调度机制的理解确实是必须的
还有一些模式看起来非常简单,但由于与其原理或实现相关的各种原因,并不是那么明确的选择:
Singleton
- 并不是完全糟糕的模式,只是被过度使用(经常出现在不适合的地方)
Observer
- 很棒的模式...只是让代码更难阅读和调试
Prototype
- 交换编译器检查的动态性(这可能是好是坏......取决于)
Chain of responsibility
- 经常被强行/人为地推入设计中
对于那些“不切实际的”,在使用它们之前应该真正考虑一下,因为通常在某个地方有更优雅的解决方案。
对于“较难掌握”的……它们真的很有帮助,当它们被用在合适的地方并且被很好地实施时……但当它们使用不当时,它们就是噩梦。
现在,接下来是什么......
Head First Design Patterns 是必须的 Sourcemaking 是“急救”【讨论】:
当您多次使用资源(通常是图像)时,享元模式是必须的。这不是一种模式,而是一种解决方案。【参考方案8】:我希望我不会因此而受到太多打击。 Christer Ericsson 在他的real time collision detection blog 中写了两篇关于设计模式主题的文章(one、two)。他的语气相当刺耳,也许有点挑衅,但这个人知道他的本事,所以我不会认为这是疯子的胡言乱语。
【讨论】:
有趣的读物。感谢您的链接! 白痴会产生糟糕的代码。有模式的白痴会比从未见过模式的白痴产生更糟糕的代码吗?我不认为他们这样做。对于聪明的人来说,模式提供了一个众所周知的词汇表,可以简化思想交流。解决方案:学习模式,只与聪明的程序员打交道。 我认为真正的白痴不可能编写出更糟糕的代码——无论他们使用什么工具 我认为他在大学考试中的例子只能证明,那些蔑视自己的问题领域并且不愿意在一个周末学习超过几个小时的人在尝试解决问题。【参考方案9】:有人说service locator是一种反模式。
【讨论】:
另外需要注意的是,有时需要服务定位器。例如,当您无法正确控制对象的实例化时(例如 C# 中具有非常量参数的属性)。但也可以使用带有 ctor 注入的服务定位器。【参考方案10】:我相信观察者模式有很多要解决的问题,它适用于非常普遍的情况,但随着系统变得越来越复杂,它变成了一场噩梦,需要 OnBefore()、OnAfter() 通知,并且经常将异步任务发布到避免再次进入。一个更好的解决方案是开发一个自动依赖分析系统,在计算期间检测所有对象访问(带有读取障碍)并自动在依赖图中创建一条边。
【讨论】:
直到“A”这个词我都理解了你的回答 您可能需要展开或链接到您所说的这个自动依赖分析。同样在 .NET 中,使用委托/事件代替观察者模式。 @Spoike:委托/事件是观察者模式的实现 我个人对 Observer 的不满是它会在垃圾收集语言中造成内存泄漏。完成一个对象后,您需要记住该对象不会被清理。 @orip:是的,这就是你使用委托/事件的原因。 ;)【参考方案11】:对 Spoike 的帖子的补充,Refactoring to Patterns 是一本不错的读物。
【讨论】:
我实际上已经链接到互联网上的图书目录。 :) 哦!我没有费心悬停它。其实,刚做完这个问题,我就想到了这本书,然后我就看到了你的回答。我无法阻止自己发布它。 :)【参考方案12】:迭代器是另一种要避免的 GoF 模式,或者至少只有在没有替代方案可用时才使用它。
替代方案是:
for-each 循环。这种结构存在于大多数主流语言中,并且在大多数情况下可用于避免迭代器。
选择器 à la LINQ 或 jQuery。当 for-each 不合适时应该使用它们,因为并非容器中的所有对象都应该被处理。与迭代器不同,选择器允许在一个地方显示要处理的对象类型。
【讨论】:
我同意选择器。 Foreach 是一个迭代器,大多数 OO 语言都提供了一个可迭代的接口,您可以实现该接口以允许 foreach。 在某些语言中,for-each 的构造可以通过迭代器来实现,但它的概念实际上更高级,更接近于选择器。使用 for-each 时,开发人员明确声明应处理容器中的所有元素。 迭代器是一个很棒的模式。反模式将在没有迭代器的情况下实现 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,迭代器是低级对象,因此在高级设计和代码中应避免使用。迭代器和不同类型选择器的实现细节在这里并不重要。选择器与迭代器不同,它允许程序员明确表达他们想要处理的内容,因此它们是高级别的。
Fwiw,另一种类似 LINQ 的 F# 语法是 `List.map (fun x -> x.Value) xs`,与列表理解的长度差不多。以上是关于避免的设计模式[关闭]的主要内容,如果未能解决你的问题,请参考以下文章