Chinaunix首页 | 论坛 | 博客
  • 博客访问: 468068
  • 博文数量: 724
  • 博客积分: 40000
  • 博客等级: 大将
  • 技术积分: 5010
  • 用 户 组: 普通用户
  • 注册时间: 2008-10-13 14:47
文章分类

全部博文(724)

文章存档

2011年(1)

2008年(723)

我的朋友

分类:

2008-10-13 16:56:54


最近在学习Design Pattern,主要阅读的书包括:

  • GoF,Design Patterns: Elements of Reusable Object-Oriented Software
  • Shalloway,Design Patterns Explained: A New Perspective on Object Oriented Design
  • Freeman,Head First Design Patterns
  • Martin,Agile Software Development: Principles, Patterns, Practices

为什么会同时读这几本书?原因很简单——因为上述任一本书都没有能把常见的Design Pattern讲清楚。

  • GoF的那本失于晦涩难懂,缺乏阐述,对于入门者实在不合适。我一直在想,为什么该书没有先讲解各种Design Pattern的结构和适用场景,而先是引入了一个Solution,看得我云里雾里的。
  • Shalloway的写得不错,结构安排也合理,但是介绍的Design Pattern少了点,书中大量充斥的笔误和错误代码也使之减色不少。
  • Freeman的是本很有趣的书,文笔也很俏皮。这是一本适合初学者的书,书中的每个Design Pattern都由一个很简单的实际应用逐渐引入,并且每章都有练习题(包括填字游戏)。不过我认为300页就能完成的书也因此用了近700页,看得有点累。
  • Martin的书是一本中级以后可以阅读的书,前面几章的内容略显枯燥,但仔细的阅读可以让你重新认识Object-Oriented Design,对提高自己对问题域的分析能力以及与团队的沟通能力会有一定的帮助。


回到正题,在GoF的书中,列出了以下几种创建型的Design Pattern:

  • Factory Method
  • Abstract Factory
  • Builder
  • Prototype
  • Singleton

对于创建型的Pattern,我们可以这样理解。假设Client(顾客)需要获取一个Shoe(产品)对象,通常的方式是这样的:

Shoe shoe = new Shoe();

而Pattern的引入,就是在Client与Shoe之间插入一个生产者(创造者、工厂)Factory,于是上述的一个new操作变成了一系列的调用过程:

//Client中
Factory fact = new Factory();
Shoe shoe    
=
 fact.CreateShoe();

//Factory中

public Shoe CreateShoe()
{
    
return new
 Shoe();
}

上述代码表现出来的Client--->Factory--->Shoe之间的关系,正是这些Pattern要实现的,只是各自实现的细节和方法不一样而已。

(一)Singleton

Singleton是比较简单的,其作用就是保证对象的全局性唯一,即任一时刻只有一个实例对象存在。由其引申出的还有适用于多线程的Double-Checked Locking Pattern。下面这段C#代码是Singleton的一个典型实现:

public class Singleton
{
    
private static Singleton m_Instance = new Singleton();

    
private Singleton()
    
{;}

    
public static Singleton GetInstance()
    
{
        
return m_Instance;
    }

}

有意者请参阅此文:

(二)Factory Method

在Factory Method中,Factory与Shoe存在一一对应的关系。这次复杂点,假设Client要获取男鞋ShoeForMale与女鞋ShoeForFemale,则必定存在均派生于Factory的FactoryMale与FactoryFemale两个工厂,其中FactoryMale为Client创建ShoeForMale对象,FactoryFemale为Client创建ShoeForFemale对象。

至于选择FactoryMale亦或FactoryFemale,则是由Client负责并选择的。一旦选定了某一Factory,就可以调用一致的factory.CreateShoe()方法来创建Shoe对象了。

(三)Abstract Factory

理解了Factory Method,我们可以把Abstract Factory看作其扩展的结果,或者说Factory Method是Abstract Factory退化的结果。与Factory Method不同的是,Abstract Factory能为Client返回的,总是多种不同的对象,同时这些对象之间总是存在Family(族)的关系,这些对象总是“同生共死”,只能生活在“同一片天空下”。这样说可能不太好理解,还是用一个通俗的例子进行说明吧。

假设这一次Client需要的是Shoe与Socks,那么Factory肯定要提供CreateShoe()与CreateSocks()两个方法为之服务。但是想想,一位男士也许不会愿意去穿一位女士的鞋或袜子吧,呵呵。所以这回对Factory得有一定的限制了,因此FactoryMale生产ShoeForMale,那么它能生产的Socks也只能是SocksForMale。同样的,FactoryFemale能生产的也只能是ShoeForFemale与SocksForFemale。

在上面这个Shoe与Socks的例子中,我们也许发现了ShoeForMale、
ShoeForFemale均派生于Shoe,而SocksForMale、SocksForFemale则派生于Socks。于是我们可能会认为,Abstract Factory为Client返回的,只能是A1+B1或者A2+B2这样的产品配对,即必有A<->B这样的对应关系。而这也正体现了Abstract Factory得以适用的主要场景——在不同平台间进行移植

但是,在GoF的书中为我们提供的例子,却出现了A1+B1、A1+C1、A2+B1这样的一些配对。即出现了Client要求A、B、C对象,而有的Factory只返回A与C,而没有B的现象。

在他的例子里,MazeFactory通过MakeMaze()、MakeRoom()、MakeWall()、MakeDoor()等接口方法能为Client提供Maze、Room、Wall与Door对象。要注意的是,Wall、Door是Room对象的组成部分,Room又成为Maze的组成部分。正是这种彼此包含的关系,体现在派生的BombedMazeFactory中后,就只实现了MakeMaze()、MakeWall()与MakeRoom()方法,而没有Door对象向Client提供。

他为什么可以这样做?或者说为什么要这样做呢?这是因为存在前述对象间的包含关系,使得Client要负责用获取的Door、Wall对象去构造Room对象,然后再用构造好的Room对象去构造Maze对象。而我之前理解的,则是认为既然是MazeFactory,必定是为Client返回好一个构造好了的Maze。正是这样的矛盾让我困惑了许久,那段时间确实很痛苦。所以就我个人认为,GoF书中这样应用Abstract Factory即便能体现Pattern适用的灵活性,却也是不严谨的。

(四)Prototype

这是一个比较简单的创建型Pattern。它就象一个仓库,里面已经有了我们需要的产品样本,我们要做的只是Clone或者说Copy一个。所以应用这个Pattern的关键,在于设计好这个样本,包括这个样本的各种Property、状态等等。然后为其设计一个恰当的接口方法Clone()。

每当Client要获取一个Shoe对象时,由于Shoe的每个实例对象已经定义好了,所以要做的只是简单地调用Shoe.Clone()而已。这有点类似于C#或者说.NET Framework下提到的“Deep Copy”(深拷贝)。

(五)Builder

个人认为,就GoF书中Maze的例子,适用Builder是最为恰当的。Builer的使用更象是搭积木,要搭的房屋是Client要的对象,其中的每一个积木块是部件对象。Builder Pattern的适用场景可以做如下模拟:

1. 有一个需要Shoe的Client;
2. Shoe是可以定制的;
3. Shoe可供定制的部件包括鞋底Tread、鞋面Vamp、鞋带Lace(甚至还有更多的可供定制的部分);

于是,Builer要做的,就是将部件的构造剥离出来,当部件构造完毕后,再将完整的Shoe交给Client。看看下面这段很简单的代码吧。

public interface Builder
{
    Tread CreateTread();
    Vamp CreateVamp();
    Lace CreateLace();
    Shoe GetShoe();
}


public class BuilderMale : Builder
{
    
public
 Tread CreateTread()
    
{
        
return new
 TreadForMale();
    }


    
public Vamp CreateVamp()
    
{
        
return new
 VampForMale();
    }

    
public Lace CreateLace()
    
{
        
return new
 LaceForMale();
    }


    
public Shoe GetShoe()
    
{
        
return new
 Shoe(CreateTread(), CreateVamp(), CreateLace());
    }

}


//在Client里
Builder builder = new BuilderMale();

Shoe shoe 
= builder.GetShoe();

从某个角度说,Builder很象Factory,即一种Shoe对应一种Builder。同时也很象Prototype,它只是把Clone()换作了GetShoe()。

同样的,在Gof使用Builder构造Maze的示例里,他又将Maze的构造责任交给了Client。换作上述的代码,即CreateTread()、CreateVamp()与CreateBootlace()由Client直接调用。当Client获取了Tread、Vamp、Bootlace对象后,通过调用Shoe的组装函数完成组装,然后调用Builder的GetShoe()获取最后的Shoe对象。不过他的示例里,也演示了如何利用Builder进行对象计数,很是有趣。


2006-9-7修订

最近又在网上找了一些Factory、Builder的例子,发现它们确实都按照GoF的方式,将子部件的构造也交由Client负责,所以开始怀疑自己的理解是否正确。

在GoF的这种方式里,Factory或者Builder在提供了CreateShoe()接口的同时,也要提供Shoe各部件的CreateVamp()、CreateTread()、CreateLace()接口。Client利用CreateShoe()返回一个没有实质意义的Shoe对象,然后再利用CreateVamp()等获取各部件,最后利用Shoe对象的方法完成组装。
2007-2-14修订

因为对上述组装过程究竟应由Client还是Builder负责存在疑惑,我又去找了一些例子和文章。肯定的答案是:Product的组装过程由Client决定,这个组装过程是应用中恒定的内容。变化的是不同种类的Product由不同种类的Builder提供相应的部件。

2007-3-20修订

在MSDN Web Cast 李建忠的系列讲座《C#面向对象设计模式纵横谈》中,给出了Singleton更为简洁的实现方式,很不错:

class Singleton
{
    
public static readonly Instance = new Singleton();

    
private Singleton() {;}
}


posted on 2006-08-30 22:30 Abbey的小匣子 阅读(599)   


--------------------next---------------------

阅读(246) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~