Answers:
所有设计模式都应谨慎使用。我认为您应该在有正当理由的情况下重构模式,而不是立即实施模式。使用模式的一般问题是它们会增加复杂性。模式的过度使用使给定的应用程序或系统繁琐,无法进一步开发和维护。
在大多数情况下,有一个简单的解决方案,您无需应用任何特定的模式。一个好的经验法则是,每当代码片段倾向于被替换或需要经常更改时就使用模式,并准备在使用模式时承担复杂代码的警告。
请记住,如果您发现支持代码更改的实际需求,您的目标应该是简单并采用一种模式。
如果使用模式显然会导致过度设计和复杂的解决方案,那么使用模式似乎似乎毫无意义。但是,对于程序员而言,阅读构成大多数模式基础的设计技术和原理会更加有趣。实际上,我最喜欢的一本关于“设计模式”的书通过重申适用于该模式的原理强调了这一点。就相关性而言,它们足够简单,可以比模式有用。只要您可以构建代码模块,其中的某些原则就足以涵盖除面向对象编程(OOP)以外的其他内容,例如Liskov Substitution Principle。
有许多设计原则,但是GoF本书第一章中描述的那些原则非常有用。
让那些人沉迷于你一段时间。应该注意的是,在编写GoF时,接口意味着任何抽象的东西(也意味着超类),不要与Java或C#中的接口混淆。第二个原则来自观察到的对继承的过度使用,遗憾的是今天仍然很普遍。
从那里您可以阅读Robert Cecil Martin (又名Bob叔叔)所熟知的SOLID原理。斯科特·汉塞尔曼(Scott Hanselman)在播客中采访了鲍勃叔叔,涉及这些原则:
这些原则是您阅读并与同行讨论的良好起点。您可能会发现这些原则相互交织,并且与其他流程(例如关注点分离和依赖注入)交织在一起。在进行了TDD一段时间后,您可能还会发现这些原则在实践中很自然地出现,因为您需要在一定程度上遵循它们以创建孤立且可重复的单元测试。
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之前先三思而后行。以后可能会减轻您的很多痛苦。
我相信模板方法模式通常是非常危险的模式。
我认为Active Record是一种过度使用的模式,它鼓励将业务逻辑与持久性代码混合在一起。从模型层隐藏存储实现并将模型与数据库绑定并不能很好地完成工作。有很多替代方案(在PoEAA中有描述),例如表数据网关,行数据网关和数据映射器,它们通常提供更好的解决方案,并且肯定有助于提供更好的存储抽象。而且,您的模型不需要存储在数据库中;如何将它们存储为XML或使用Web服务访问它们呢?更改模型的存储机制有多容易?
也就是说,Active Record并不总是坏的,它非常适合其他选项过于简单的简单应用程序。
这是简单...避免设计模式都不清楚你或那些你不觉得舒服。
列举一些...
有一些不切实际的模式,例如:
Interpreter
Flyweight
还有一些更难掌握的东西,例如:
Abstract Factory
-带有创建对象族的完全抽象工厂模式似乎并不那么容易Bridge
-如果将抽象和实现划分为子树,可能会变得过于抽象,但在某些情况下是非常有用的模式Visitor
-了解双重派遣机制确实是必须的并且有些模式看起来非常简单,但是由于与它们的原理或实现相关的各种原因,它们的选择并不十分明确:
Singleton
-并非完全错误的模式,只是过于使用(通常在那里,不合适)Observer
-很棒的模式...只会使代码更难阅读和调试Prototype
-交易编译器检查动态性(可能好坏……取决于)Chain of responsibility
-经常只是被迫/人为地推入设计对于那些“不切实际的人”,应该在使用它们之前真正考虑一下,因为通常在某个地方有更优雅的解决方案。
对于“较难掌握”的人……当在适当的地方使用它们以及实施得当时,它们确实是非常有用的帮助……但是,如果使用不当,它们将成为噩梦。
现在,下一步是什么...
我希望我不会为此受到太大伤害。克里斯特•爱立信(Christer Ericsson )在他的实时碰撞检测博客中写了两篇关于设计模式的文章(一,二)。他的语气相当刺耳,也许有点挑衅,但是这个人知道他的东西,所以我不会把它当作疯子的狂欢。
有人说服务定位器是一种反模式。
我相信观察者模式有很多需要解决的问题,它在非常普通的情况下仍然有效,但是随着系统变得越来越复杂,它成为一场噩梦,需要OnBefore(),OnAfter()通知以及经常发布异步任务来避免重新执行入口。更好的解决方案是开发一种自动依赖性分析系统,该系统可在计算过程中检测所有对象访问(具有读取障碍)并自动在依赖性图中创建边。
对Spoike的文章《重构为模式》的补充是一本好书。
迭代器是另外一种可以避免使用的GoF模式,或者至少在没有其他选择可用时才使用它。
替代方法是:
每个循环。大多数主流语言中都存在此构造,并且在大多数情况下可以避免使用迭代器。
LINQ或jQuery的选择器。当不适合for-each时应使用它们,因为不是应该处理容器中的所有对象。与迭代器不同,选择器允许在一个地方表明要处理的对象类型。
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/…。