Brave Ostrich

做一只勇敢的鸵鸟
Counting...

《Design Patterns》跟我读――创建型模式(更新于06-12-03)

GOF《Design Patterns》第三章

*GOF巨作《Design Patterns》毫无疑问是设计模式的圣经,然而“从风格上讲,该书与其说是为学习者而写作的教程范本,还不如说是给学术界人士看的学术报告,严谨有余,生动不足。”〔孟岩〕本系列将《Design Patterns》中文版(结合英文版)中重要句子按句解析,作为自学笔记也给新接触设计模式的朋友一点借鉴。文中原文以粗体标出。我自己不明白的地方以〔TODO:〕标出,希望高手多多指点。

创建型模式抽象了实例化过程。两个问题:

  1. 何谓实例化过程?
  2. 什么叫做抽象?

所谓实例化过程,说白了就是创建对象的过程。在面向对象语言中,一般是指通过构造器(constructor)创建对象的过程。在Java和C#中通常用new这个关键字来创建对象,当然还有通过反射的方法,后者用的较少。一个对象实例化结束后并不一定就可以使用了,对于复杂的对象有时候还需要做一些装配,有时候也叫做初始化。后面我们说到实例化没有特殊说明,就是指的用new这个关键创建对象。[e.g instance = new SomeConcreteClass();]

这个过程如何抽象呢?所谓抽象就是掩盖具体的东西,这里面那个东西是具体的概念呢?显然是构造器,也就是SomeConcreteClass这个类。这是一个具体类,当我们的代码里面出现具体类的时候,我们就依赖了具体实现,这违背了DIP原则。抽象就是客户端只了解自己需要某个实例,但是不了解改实例的创建过程,或者说不了解其构造函数的签名(包括构造器的名字即类名和构造参数)。

它们帮助一个系统独立于如何创建、组合和表示它的那些对象。原文是:They help make a system independent of how it’s objects created, composed and represented.这句话说明创建型模式的作用是使得系统独立于系统中对象的创建,组合和表示。从这一点上看,上一句说抽象了实例化过程中的实例化过程是包含装配过程(组合)的。创建型模式帮助系统独立于其对象的创建和组合是容易理解的。比如Factory Method通过一个返回接口的方法来返回对象实例,客户端并不了解对象的创建过程;Builder模式隔离了对象的各个部分的创建和其组合过程。那么独立于表示如何理解呢?“表示”在这里可以理解为对同一个接口的不同实现。

[e.g. (感谢Lenny Primark)

List<Number> numbers = numbersFactory.createNumbers(5, 10); // create five numbers with values of 10

工厂方法返回的实例可能是如下的任何一个“表示”:

  • it can give you LinkedList<Number> or ArrayList<Number>
  • it can give you List<IntegerNumber> or List<DoubleNumber>
  • or any other combination.

创建型模式返回给你的是借口,而实现和实际数据的表示细节则被隐藏了。]

一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化委托给另一个对象。

随着系统演化得越来越依赖与对象复合而不是类继承,创建型模式变得更为重要。首先给我们指明了演化的方向,越来越依赖于对象复合而不是继承,然后在这个前提下,应用创建型模式,我想这是鼓励对象创建型模式。

当这种情况发生时,重心从对一组固定行为的硬编码(hard-coding)转移为定义一个较小的基本行为集,这些行为可以被组合成任意数目的更复杂的行为。类继承实际上是“静态的”固定的行为,是在编译期确定了的行为,而对象复合追求的是通过较小的基本行为集组合成复杂的行为,通常是支持运行时改变的组合方式的。

这样创建有特定行为的对象要求的不仅仅是实例化一个类。即,还有可能要负责装配过程。

在这些模式中有两个不断出现的主旋律。第一,它们都将关于该系统使用哪些具体的类的信息封装起来。第二,它们隐藏了这些类的实例是如何被创建和放在一起的。第一个里面封装也就是说,客户代码不了解自己使用的具体类是什么。第二个就很明白了,但是与第一段中相比,没有提到“represented”(表示)这个意思。

整个系统关于这些对象所知道的是有抽象类所定义的接口。这里整个系统实际上说的是客户代码,整个系统如果包含具体类在内当然要了解具体类了。

因此创建型模式在什么被创建,谁创建它,它是怎样被创建的,已经何时创建这些方面给予你很大的灵活性。什么被创建:通过产品接口隐藏具体类。谁创建它:通过工厂接口隐藏具体工厂。它是怎样被创建:Builder抽象方法隐藏创建过程。何时创建:单件模式隐藏创建时机。〔TODO:这里还要加上其他几个模式分别封装什么。〕

它们允许你用结构和功能差别很大的“产品”对象配置一个系统。配置可以是静态的(即在编译时指定),也可以是动态的(在运行时)。结构和功能差别很大的“产品”应该实现相同的接口。

有时创建型模式是相互竞争的。例如,在有些情况下Prototype或Abstract Factory用起来都很好。而在另外一些情况下它们是互补的:Builder可以用其他模式去实现某个构件的创建。Prototype可以在它的实现中使用Singleton。

接下来作者举了一个例子,这里不重述了。但是还是有几点需要注意。

作者把Enter这个方法放入MapSite中,而不是某个叫做Player的类中。这里面包含了很多的智慧和经验。当然我不排除Player类中应该包含一个类似与Move之类的方法,但是这个方法应该调用Mapsite的Enter方法。试想,对于Move这个动作和其响应如果全部放在Player这个类中完成将是如下的效果:

[

public void Move(Direction d)

{

    if(currentRoom.GetSide(d) is Room){}

    else if (currentRoom.GetSide(d) is Wall){}

    else if (currentRoom.GetSide(d) is Door)

    {

        if (door.isOpen){}

        else{}

    }

}

] 毫无疑问这种方式的可读性,扩展性都很差。Enter为更复杂的游戏操作提供了一个简单基础。在一个真正的游戏中,Enter可以将游戏者对象作为一个参数。

作者在给出CreateMaze方法之后的评价,考虑到这个函数所做的仅是创建一个有两个房间的迷宫,它是相当复杂的。这个复杂性是因为客户端负责了太多的职能。Room的构造器可以提前用墙壁来初始化房间的每一面。这样会使得客户端代码稍微简单一些。这个成员函数真正的问题不在于它的大小而在于它不灵活。

创建型模式显示如何使得这个设计更灵活,但未必会更小。所以使用模式的时候,要抛弃这个观点,认为好的设计是小的设计,而是灵活的可扩展的设计。

当前的设计不易扩展,其最大的障碍是对被实例化的类进行硬编码。在本章导论部分的最后,作者给出了创建型模式的解决方案。

  • Factory Method:CreateMaze调用虚函数而不是通过new来创建Room,Wall和Door。Factory Method实际上简单的令你惊讶!任何时候,当你存在一个虚拟的(抽象的)函数其返回值是一个接口(抽象类),你就在使用Factory Method了。(插一句,理解一个模式的简单性有时候比理解其复杂性更重要,很多人对Factory Method模式敬而远之,就是没有理解其简单性。)建议您参考一下我的另一篇文章:《没有Factory这个模式》。
  • Abstract Factory:CreateMaze不是调用自己的虚函数,而是从外界传入一个对象(抽象工厂)给它。CreateMaze方法调用这个对象的方法(也是虚方法)来创建Room,Wall和Door。
  • Builder:(TODO:等我看完了再补充:)
  • Prototype:(TODO:等我看完了再补充:)
  • Singleton:保证每个游戏中仅有一个迷宫,而且所有的对象都可以迅速访问它――二不需要求助于全局变量或函数。Singleton也使得迷宫易于扩展或者替换,且不需要变动已有代码。Singleton如此受到争议,我就不多说了。作者在最后加一句“易于扩展或者替换”,还强调“不需要变动已有代码”确实让人不解。(TODO:哪位高手出来解释一下?)

posted on 2006-12-02 21:48 勇敢的鸵鸟 阅读(1920) 评论(5)  编辑 收藏 网摘 所属分类: A Design Patterns

评论

#1楼  2006-12-02 23:49 TerryLee      

从风格上讲,该书与其说是为学习者而写作的教程范本,还不如说是给学术界人士看的学术报告,严谨有余,生动不足
=================================================
说的是没错,可是我们也不应该责备求全,毕竟写作年代不一样!   回复  引用  查看    

#2楼  2006-12-03 11:07 风云      

哈哈, 楼主说的没错,不过GOF 这本书,每读一遍,都有不同的收获,不同的感觉,读的次数越多越感觉到回味无穷.GOF 这本书 不太适合初学者,<<JAVA 与模式>> 通俗易懂,非常适合初学者.   回复  引用  查看    

#3楼 [楼主] 2006-12-03 13:00 肖鹏      

这句话是孟岩老师的评价,我哪里敢评价:)   回复  引用  查看    

#4楼 [楼主] 2006-12-03 13:04 肖鹏      

关于如何使得系统独立于表示,还有一些朋友的讨论就不更新到正文了。
Russ Rufer
Clients classes (the ones that receive created products) know the
products by their interfaces, but are shielded from details about those
products.

For example, suppose you have ConcreteCreatorA and ConcreteCreatorB in
Factory Method. It might be the case that they return objects of Class
ProductA and ProductB, respectively (both implementing a Product
interface). Or, it could be that there's only one class of
ConcreteProduct and the local decision made by these two creators is to
configure the product differently before returning it. The point is,
because the construction is isolated, the consumer of these products
doesn't know about the internal representation behind the interface. The
actual implementation of ProductA could be a simple class that
ConcreteCreatorA constructed and returned, or it could be a class that
makes heavy use of foreign calls to another language (say JNI calls in
java) or a Remote Proxy for work done on another machine.

You'll find some specific discussion of the representation issue when
you read Builder, but it really applies to all the Creational Patterns.

- Russ

  回复  引用  查看    

#5楼  2006-12-03 16:24 Justin      

支持一下!   回复  引用  查看    





标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2007-01-27 16:04 编辑过
Google站内搜索

相关文章:

相关链接: