`
庄表伟
  • 浏览: 1150385 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

敲响OO时代的丧钟——设计模式批判(1)

阅读更多
为什么要批判设计模式?设计模式不是OO开发的救星吗?作为“可复用的面向对象”的基础设施,设计模式大大的超越了OO设计原则给予我们的承诺,还记得我们前面的分析吗?OO设计原则并不担保你在设计之前就能避免错误,相反的,你往往需要在屡屡受伤之后,才会明白设计原则的真谛。而设计模式是如此的伟大,他甚至可以帮你提前避免问题,只要你可能遇到的问题,符合设计模式手册中,所描述的那种场景,基本上你就可以直接采用相应的设计方案了。如果找不到正好合适的,你也可以改造自己面对的问题,使得他看起来究就像设计模式手册中描述的那样,然后你就可以放心使用相应的设计方案了。如果你无法在那23个模式中找到合适的答案——你可真是太挑剔了——那么你只能自己想法组合一下那23个中的2~3模式,总之,一切尽在其中
 
好吧,事实其实没有那么夸张,“GoF”从来没有宣称“设计模式”能够包治百病,更没有说过使用“设计模式”可以预防疾病,他们也的确谦虚的承认,设计模式肯定不止23个。但是,GoF也必须承认的一点就是:“Design Patterns原本是用来指导设计的。大多数时候,都是在实际开发之前完成的。”而且,按照设计模式原本的思维模式,应该把一个系统中的各个类,按照他们所说的一堆模式组织起来,其根本目的,就是不让后来的改动,再去修改以前的代码,所谓OCP原则在设计模式中的实际体现,就是对扩展开放、对修改封闭。
 
In other words, (in an ideal world...) you should never need to change existing code or classes: All new functionality can be added by adding new subclasses and overriding methods, or by reusing existing code through delegation.
 
再强调一遍:“设计模式认为,灵活性、可复用性是设计出来的”,而在此之后的发展我们可以看到,新的大师们又偷换了概念,将“设计——实施”的两阶段过程,变成了一个“TDD+重构”的持续改进过程,他们不但提倡改以有的代码,而且要大改特改,持续不断的改,唯一还带着的帽子是:“重构的目标是得到设计模式。”重构真的能以设计模式为目标吗?我们下一篇再讨论。
 
 请允许我先借力打力,利用重构这一新生事物,攻击一下设计模式这个老东西。为什么灵活性、可复用性不能是设计出来的?
 
软件开发,一个很重要的工作,就是对付“需求变更”,软件工程的办法是尽可能的抵挡、或者有效控制变更的发生。技术人员的目标,则是努力开发出更加灵活的技术,以适应可能的变化。值得一提的是,现在软件开发的管理者,也开始相信,拥抱变化比限制变更,是更为可取的手段了。
 
更加灵活的技术,更加容易理解,方便描述事实的语言,设计模式等等等等,都是用来适应可能的变化的。对于技术人员来说,如果能够预测可能的变化,并在系统设计期就将其考虑进去,那么变化就成为计划的一部分,注意这句话,因为实际的情况往往是:“计划赶不上变化”。越是自信的技术人员,越是自以为能够预测可能的变化,将变化提前设计进入自己的系统。所以,滥用设计模式的人,往往是那些自以为水平很高的半桶水。而重构这一思路的出现,就是对于设计模式这种“企图预测变化”的否定。事实上,即使是重构,也是危险的,因为从原始状态,到第一个变化发生时,我们能够得到的只有两个状态点,这两个点联成直线所指向的一个方向,并不一定就是变化的方向,因此,重构是一个好办法,而将得到设计模式作为重构的目标,却相当危险。
 
设计模式背后的思路非常清楚,就是将可能变化纳入设计模式的考虑之中,所以我们看到设计模式的目标“可复用”,其实是一个转了一个弯以后的目标,“在尽可能重用已有代码的前提下,适应变化”。我的观点是:“首先需要满足的不是复用,而是快速修改”,这个问题太大以后有机会再讨论吧。
 
这篇关于设计模式的批判,我写了好几天,始终感觉难以下手。今天和徐昊讨论,他的话我认为非常有道理:“设计模式的成功,正好证明了OO的失败”。这个思路相当有用,我决定就按这个调子来写。当然,设计模式并不是只有一个,而是有很多很多个,作为一种“专家经验交流的规范描述格式”,设计模式已经非常多了。我们今天也不批判更多的模式,仅仅对GoF的23个模式下手吧。
 
GoF的23个设计模式,主要分为三类:创建型模式、结构型模式、行为型模式。我们就分别批判这三种模式吧。
 
创建型模式之所以需要,其实正好证明了OO的失败。因为在OO的思想中,创建对象这种事情,天然就应该是对象自己处理的。一个类在定义时,可以定义一个以上的构造函数,来决定如何得到自己的实例,那为什么还需要把创建对象这个事情,交到别人手里?按照SRP原则,无论出于什么原因,创建自己总是自己的职责吧!所以按照SRP原则,所有的创建型模式都不应该出现,出现了也是错误的。但是为什么我们需要创建型模式呢?先看看GoF的解释:“随着系统演化得越来越依赖于对象复合而不是类继承,创建型模式变得更为重要。当这种情况发生时,重心从对一组固定行为的硬编码,转移为定义一个较小的基本行为集,这些行为可以被组合成任意数目的复杂的行为,这样创建有特定行为的对象要求的不仅仅是实例化一个类。”
 
这样的解释,有一定的道理,但是却局限于“用组合取代继承”这样一个“当年的热门话题”。在我看来,根本的原因在于:“在OO的经典思想中,对象之外的环境是不存在的,因此,所有的对象,都要考虑如何产生自己,如何销毁自己,如何保存自己,总之,自己的事情自己做。”Java的一个巨大进步就在于,销毁自己这件事情,不再强求对象自己去考虑了,因为这种事情过于琐碎,而且复杂易错。但是创建自己这件事情,java依然没有考虑到也不该交给对象自己考虑的。于是设计模式中的种种创建模式被发明出来,用以满足复杂多变的创建需求。这个根本原因同时也解释了为什么单例模式会被发明,按照GoF的解释,是无法说明为什么我们会需要单例模式地。而当我们有了对象环境的概念之后,各种创建自然有“容器环境”来完成,“单例”模式也只需要在环境中配置,有了OO容器之后,所有的创建模式都被一个概念所代替了。在没有这样的概念之前,我们需要用各种模式技巧,来实现“支离破碎”的环境。而在真正的容器环境出现之后,我们就不再需要这些设计模式了。
 
让我再说一遍:“如果你能够理解为什么现在会出现那么多容器,就能理解设计模式中的创建模式,只不过是用来解决OO概念欠缺的一种不完善的手段了。”
 
再来看结构型模式,个人认为将“Adapter、Bridge、Composite、Decorator、Facade、Flyweight、Proxy”统统归入结构型模式,是不恰当的。除了Composite模式算是结构模式,Flyweight算是一种“节约内存的技术手段”之外,其他的模式,都属于打破OO静态封装的技巧。我们知道,按照OO的设定,一个类,就是一种类型,它在代码写下的时候,就已经决定了自己的内部数据与外部行为。怎么还能在运行的时候再改变呢?事实证明,这样需求不但存在,而且重要,设计模式之所以被大家欣赏,一个重要的原因,就是他能够帮助程序员部分摆脱“静态封装属性与行为”的限制。
 
(未完待续)
分享到:
评论
1 楼 bpm 2008-08-21  
你应该写成英文的与GoF讨论,各说各有理。

相关推荐

Global site tag (gtag.js) - Google Analytics