.NET设计模式(7):创建型模式专题总结(Creational Pattern)

 

创建型模式专题总结(Creational Pattern

——.NET设计模式系列之七

Terrylee20061

概述

创建型模式,就是用来创建对象的模式,抽象了实例化的过程。它帮助一个系统独立于如何创建、组合和表示它的那些对象。本文对五种常用创建型模式进行了比较,通过一个游戏开发场景的例子来说该如何使用创建型模式。

为什么需要创建型模式

所有的创建型模式都有两个永恒的主旋律:第一,它们都将系统使用哪些具体类的信息封装起来;第二,它们隐藏了这些类的实例是如何被创建和组织的。外界对于这些对象只知道它们共同的接口,而不清楚其具体的实现细节。正因如此,创建型模式在创建什么(what),由谁(who)来创建,以及何时(when)创建这些方面,都为软件设计者提供了尽可能大的灵活性。

假定在一个游戏开发场景中,会用到一个现代风格房屋的对象,按照我们的一般想法,既然需要对象就创建一个:

ModernRoom room = new ModernRoom();

好了,现在现代风格房屋的对象已经有了,如果这时房屋的风格变化了,需要的是古典风格的房屋,修改一下:

ClassicalRoom room = new ClassicalRoom();

试想一下,在我们的程序中有多少处地方用到了这样的创建逻辑,而这里仅仅是房屋的风格变化了,就需要修改程序中所有的这样的语句。现在我们封装对象创建的逻辑,把对象的创建放在一个工厂方法中:

ModernFactory factory = new ModernFactory();

ModernRoom room = factory.Create();

当房屋的风格变化时,只需要修改

ClassicalFactory factory = new ClassicalFactory();

ClassicalRoom room = factory.Create();

而其它的用到room的地方仍然不变。这就是为什么需要创建型模式了。创建者模式作用可以概括为如下两点:

1.封装创建逻辑,绝不仅仅是new一个对象那么简单。

2.封装创建逻辑变化,客户代码尽量不修改,或尽量少修改。

常见的五种创建型模式

单件模式Singleton Pattern)解决的是实体对象的个数问题,其他的都是解决new所带来的耦合关系问题。

工厂方法模式Factory Pattern)在工厂方法中,工厂类成为了抽象类,其实际的创建工作将由其具体子类来完成。工厂方法的用意是定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类中去,强调的是“单个对象”的变化。

抽象工厂模式Abstract Factory)抽象工厂是所有工厂模式中最为抽象和最具有一般性的一种形态。抽象工厂可以向客户提供一个接口,使得客户可以在不必指定产品的具体类型的情况下,创建多个产品族中的产品对象,强调的是“系列对象”的变化。

生成器模式Builder Pattern)把构造对象实例的逻辑移到了类的外部,在这个类的外部定义了这个类的构造逻辑。他把一个复杂对象的构造过程从对象的表示中分离出来。其直接效果是将一个复杂的对象简化为一个比较简单的目标对象。他强调的是产品的构造过程。

原型模式Prototype Pattern)和工厂模式一样,同样对客户隐藏了对象创建工作,但是,与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。

如何选择使用创建型模式

继续考虑上面提到的游戏开发场景,假定在这个游戏场景中我们使用到的有墙(Wall),屋子(Room),门(Door)几个部件。在这个过程中,同样是对象的创建问题,但是会根据所要解决的问题不同而使用不同的创建型模式。

如果在游戏中,一个屋子只允许有一个门存在,那么这就是一个使用Signleton模式的例子,确保只有一个Door类的实例被创建。解决的是对象创建个数的问题。

示例代码:

using System;

public sealed class SigletonDoor

{

    static readonly SigletonDoor instance=new SigletonDoor();

    static SigletonDoor()

    {

    }

    public static SigletonDoor Instance

    {

        get

        {

            return instance;

        }

    }

}

在游戏中需要创建墙,屋子的实例时,为了避免直接对构造器的调用而实例化类,这时就是工厂方法模式了,每一个部件都有它自己的工厂类。解决的是“单个对象”的需求变化问题。

示例代码:

using System;

public abstract class Wall

{

    public abstract void Display();

}

public class ModernWall:Wall

{

    public override void Display()

    {

        Console.WriteLine("ModernWall Builded");

    }

}

public abstract class WallFactory

{

    public abstract Wall Create();

}

public class ModernFactory:WallFactory

{

    public override Wall Create()

    {

        return new ModernWall();;

    }

}

在游戏场景中,不可能只有一种墙或屋子,有可能有现代风格(Modern),古典风格(Classical)等多系列风格的部件。这时就是一系列对象的创建问题了,是一个抽象工厂的例子。解决的是“系列对象”的需求变化问题。

示例代码:

using System;

 

public abstract class Wall

{

    public abstract void Display();

}

 

public class ModernWall:Wall

{

    public override void Display()

    {

        Console.WriteLine("ModernWall Builded");

    }

}

 

public class ClassicalWall:Wall

{

    public override void Display()

    {

        Console.WriteLine("ClassicalWall Builded");

    }

}

 

public abstract class Room

{

    public abstract void Display();

}

 

public class ModernRoom:Room

{

    public override void Display()

    {

        Console.WriteLine("ModernRoom Builded");

    }

}

 

public class ClassicalRoom:Room

{

    public override void Display()

    {

        Console.WriteLine("ClassicalRoom Builded");

    }

}

 

public abstract class AbstractFactory

{

    public abstract Wall CreateWall();

    public abstract Room CreateRoom();

}

 

public class ModernFactory:AbstractFactory

{

    public override Wall CreateWall()

    {

        return new ModernWall();

    }

    public override Room CreateRoom()

    {

        return new ModernRoom();

    }

}

 

public class ClassicalFactory:AbstractFactory

{

    public override Wall CreateWall()

    {

        return new ClassicalWall();

    }

    public override Room CreateRoom()

    {

        return new ClassicalRoom();

    }

}

如果在游戏场景中,构成某一个场景的算法比较稳定,例如:这个场景就是用四堵墙,一个屋子,一扇门来构成的,但具体是用什么风格的墙、屋子和门则是不停的变化的,这就是一个生成器模式的例子。解决的是“对象部分”的需求变化问题。

示例代码:

using System;

using System.Collections;

 

public class Director

{

    public void Construct( Builder builder )

    {

        builder.BuildWall();

        builder.BuildRoom();

        builder.BuildDoor();

    }

}

 

public abstract class Builder

{

    public abstract void BuildWall();

    public abstract void BuildRoom();

    public abstract void BuildDoor();

    public abstract GameScene GetResult();

}

 

public class GameBuilder : Builder

{

    private GameScene g;

 

    public override void BuildWall()

    {

        g = new GameScene();

        g.Add( "Wall" );

    }

    public override void BuildRoom()

    {

        g.Add( "Room" );

    }

    public override void BuildDoor()

    {

        g.Add( "Door" );

    }

    public override GameScene GetResult()

    {

        return g;

    }

}

 

public class GameScene

{

    ArrayList parts = new ArrayList();

    public void Add( string part )

    {

        parts.Add( part );

    }

    public void Display()

    {

        Console.WriteLine( " GameScene Parts" );

        foreach( string part in parts )

            Console.WriteLine( part );

    }

}

如果在游戏中,需要大量的古典风格或现代风格的墙或屋子,这时可以通过拷贝一个已有的原型对象来生成新对象,就是一个原型模式的例子了。通过克隆来解决“易变对象”的创建问题。

示例代码:

using System;

 

public abstract class RoomPrototype

{

    public abstract RoomPrototype Clone();

}

 

public class ModernPrototype:RoomPrototype

{

    public override RoomPrototype Clone()

    {

        return (RoomPrototype)this.MemberwiseClone();

    }

}

 

public class ClassicalPrototype:RoomPrototype

{

    public override RoomPrototype Clone()

    {

        return (RoomPrototype)this.MemberwiseClone();

    }

}

究竟选用哪一种模式最好取决于很多的因素。使用Abstract FactoryPrototype PatternBuilder Pattern的设计比使用Factory Method的设计更加灵活,但是也更加复杂,尤其Abstract Factory需要庞大的工厂类来支持。通常,设计以使用Factory Method开始,并且当设计者发现需要更大的灵活性时,设计便会向其他设计模式演化,当你在多个设计模式之间进行权衡的时候,了解多个设计模式可以给你提供更多的选择余地。

总结

使用创建者模式是为了提高系统的可维护性和可扩展性,提高应对需求变化的能力!

参考文献:
《设计模式中文版》
《DesignPatternsExplained》
  idior 的《你了解创建者模式了吗? --- 创建者模式详解
  MSDN WebCast:http://www.microsoft.com/china/msdn/events/webcasts/shared/Webcast/MSDNWebCast.aspx

作者:TerryLee
出处:http://terrylee.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
posted @ 2006-01-16 15:30 TerryLee 阅读(11401) 评论(15)  编辑 收藏 所属分类: Design Patterns

  回复  引用  查看    
#1楼 2006-01-16 15:44 | terrypang      
呵呵,和MSDN WebCast相得益彰,看完MSDN WebCast在看这个,收获颇丰!
谢谢分享这么好的心得体会!!!~~~
期待着后续的内容~~~
  回复  引用  查看    
#2楼 2006-01-16 15:45 | idior      
怎么有些地方觉得跟我的文章那么象?引用观点应该有个出处吧?
http://idior.cnblogs.com/archive/2005/04/14/137913.aspx

---
ModernFactory factory = new ModernFactory();

ModernRoom room = factory.Create();

当房屋的风格变化时,只需要修改

ClassicalFactory factory = new ClassicalFactory();

ClassicalRoom room = factory.Create();

而其它的用到room的地方仍然不变。这就是为什么需要创建型模式了。
---
这段解释很勉强。


  回复  引用  查看    
#3楼 [楼主]2006-01-16 16:08 | Terrylee      
@idior

呵呵,这些是我在听了李建忠老师的WebCast之后写的,绝没有抄袭阁下文章的意思。

至于那段解释,可能是没有说清楚关键的地方,所以总觉得收得太快!非常感谢阁下提出的意见。
  回复  引用  查看    
#4楼 2006-01-16 16:14 | idior      
---
创建者模式作用可以概括为如下两点:

1.封装创建逻辑,绝不仅仅是new一个对象那么简单。

2.封装创建逻辑变化,客户代码尽量不修改,或尽量少修改。

使用创建者模式是为了提高系统的可维护性和可扩展性,提高应对需求变化的能力!
---

这两端和我的文章几乎一字不差,我没看过那个webcast,也实在是太巧了.
我google了一下“创建者模式”原来我这篇文章排在第一 ;p
以下是我对为什么需要创建者模式的解释

---
Why do we need Creator pattern?


相信很多初学者会有这么一个问题, 为什么我们需要创建者模式?

然而很多人在向别人介绍创建者模式的时候, 常常对于这个问题一带而过.(比如我的老师).



回答: 创建者模式是用来创建对象的模式. 而模式是前人经验的总结,所以创建者模式是一个好东西.



Do you need answer like this? What can we learn about Creator Pattern From this ?

首先对我来说这不是我需要的答案, 并且从中我也仅仅知道了创建者模式是用来创建对象的模式. (晕, 你读读这句话不是废话嘛)



那么我的答案是什么?



用代码说明问题. (源代码有时胜过千言万语)



首先创建了一辆奔驰.

Car car=new Benze();

突然我们的车变了, 变成宝马了. Ok 我修改一下.

Car car=new BMW();



设想一下在我们的代码中散布了无数这样的代码.不止一处(这点很重要)

那么当你以后需要换车的时候, 是不是需要一一修改我们的创建代码把Benze改成BMW.



然后我们再用工厂来实现一下:

Car car=benzeFactory().Factory();

呵呵 这算什么? 没事找事做. 如果要换车,你不是还要修改原来的代码改成下面这样.

Car car=bmwFactory().Factory();



是吗?

如果创建代码只有这里一处可能是这样, 但是如果很多地方都要创建的话就不是了.



CarFactory carFac=new BenzeFactory();



Car car=carFac.Factory();

当你需要换Car的时候你只需修改一处代码就是CarFactory carFac=new XXXFactory();

其他创建车的地方,永远不变,还是Car car=carFac.Factory();

Ok? 你明白了吗?

我们很难避免修改, 但是我们要尽量做到只修改一处.



不知道 这样的解释你是否满意.

使用创建者模式是为了提高代码的可维护性(即应对未来修改的能力).

---
  回复  引用  查看    
#5楼 2006-01-16 16:28 | idior      
曾经想再写一篇有关创建者模式的文章
http://idior.cnblogs.com/archive/2005/08/19/218573.html

不过由于某些原因,一直没有写, 如果Terrylee 有兴趣,希望你能介绍一下enter lib2.0中的object builder(其实是跟CAB学的),
这样才与时俱进嘛 ;)
  回复  引用  查看    
#6楼 [楼主]2006-01-16 16:28 | Terrylee      
@idior

呵呵,是不是我以前看过阁下的这篇文章,对这两句话影响太深了,在写的时候就用上了,既然你的文章比较早,我应该加上引用出处。同时对李老师的WebCast也应该加上。


  回复  引用  查看    
#7楼 [楼主]2006-01-16 16:36 | Terrylee      
@idior

好的,有时间我好好研究一下:)

非常感谢!以后还望多多指教!
  回复  引用    
#8楼 2006-01-16 16:47 | lee_j [未注册用户]
所有的模式的共同目的就是减少不必要的耦合,令系统能够尽可能适应不断变化的客户需求.做到了这一点我们也可以不断去创造我们自己的模式.
  回复  引用  查看    
#9楼 2006-10-23 21:10 | 努力学习的熊      
@Terrylee
终于看完了创建型模式,感觉如idior所说,加上idior的解释一起看就明白多了,后边的总结感觉很好,尤其是在实例中各种变化,8错8错,哈哈:)
  回复  引用  查看    
#10楼 [楼主]2006-10-23 23:54 | TerryLee      
@努力学习的熊
呵呵,继续努力看完哦:)

我也好久没有更新了,晚上发一篇
  回复  引用    
#11楼 2006-12-01 14:47 | king[匿名] [未注册用户]
@idior
我觉得你下面的解释有些牵强.
我们来看Design goal,
1. design to interface
2. find what varies and encapsulate it.
3. favor composition over inheritence.
另外,Design priciple,
OCP
LSP
DIP
ISP
CARP
LOD
我想patern主要care的是扩展,灵活性和可插入性,design关注的是behavior
如果Factory Method仅实现你下面的解释的话,可以有一种更简单的做法.
1.create static class CarUtility
2.Add method: public static Car GetCar(){...}
这样的话invoke可以使用.CarUtility.GetCar(),同样的结果哦.
但是这样一来,如果扩展Car呢,如果不同的要求出不同的Car?参数注入?
所以,如果理解模式,还是从扩展性-->灵活性-->pluggability来看,比较合理.不知道你怎么看呢?

----------------------------------------------------------
----------------------------------------------------------
Car car=new Benze();
突然我们的车变了, 变成宝马了. Ok 我修改一下.
Car car=new BMW();
设想一下在我们的代码中散布了无数这样的代码.不止一处(这点很重要)
那么当你以后需要换车的时候, 是不是需要一一修改我们的创建代码把Benze改成BMW.
然后我们再用工厂来实现一下:
Car car=benzeFactory().Factory();
呵呵 这算什么? 没事找事做. 如果要换车,你不是还要修改原来的代码改成下面这样.
Car car=bmwFactory().Factory();
是吗?
如果创建代码只有这里一处可能是这样, 但是如果很多地方都要创建的话就不是了.
CarFactory carFac=new BenzeFactory();
Car car=carFac.Factory();
当你需要换Car的时候你只需修改一处代码就是CarFactory carFac=new XXXFactory();
其他创建车的地方,永远不变,还是Car car=carFac.Factory();
Ok? 你明白了吗?
  回复  引用  查看    
#12楼 2008-06-04 12:04 | 小眼睛老鼠      
假定在一个游戏开发场景中,会用到一个现代风格房屋的对象,按照我们的一般想法,既然需要对象就创建一个:

ModernRoom room = new ModernRoom();

好了,现在现代风格房屋的对象已经有了,如果这时房屋的风格变化了,需要的是古典风格的房屋,修改一下:

ClassicalRoom room = new ClassicalRoom();

试想一下,在我们的程序中有多少处地方用到了这样的创建逻辑,而这里仅仅是房屋的风格变化了,就需要修改程序中所有的这样的语句。现在我们封装对象创建的逻辑,把对象的创建放在一个工厂方法中:

ModernFactory factory = new ModernFactory();

ModernRoom room = factory.Create();

当房屋的风格变化时,只需要修改

ClassicalFactory factory = new ClassicalFactory();

ClassicalRoom room = factory.Create();

----------------------------------------------------------
我是新人不知道是不是楼主写错了
楼主的意思是用
ModernFactory factory = new ModernFactory();

ModernRoom room = factory.Create();

上面这2段话代替下面这段话
ModernRoom room = new ModernRoom();

而房屋的风格变化导致了
ModernFactory factory = new ModernFactory();

ModernRoom room = factory.Create();

变成了
ClassicalFactory factory = new ClassicalFactory();

ClassicalRoom room = factory.Create();

但是其效果
如果一开始我用的这个
ModernRoom room = new ModernRoom();
根据房屋的风格变化就应该改成
ClassicalRoom room = new ClassicalRoom();
个人觉得下面这种形式和上面的工厂形式除了在封装创建对象上没有什么太大区别(没法从代码中体现这2个类的结构统一,不能保证ModernRoom的方法和ClassicalRoom 的方法是统一的,也不能体现改全局的优势)
并不能达到改一处就统一改全局的
感觉就是
ModernFactory factory = new ModernFactory();

ModernRoom room = factory.Create();
替换了
ModernRoom room = new ModernRoom();
以前要改多少现在还是要改多少
而且以前是1句现在变2句。。。。


一下是个人理解
个人对楼主文章的理解倾向于(一个工厂一般是要考虑有多个对象的创建的情况,也许不是在一个位置创建)
最基本的

ModernRoom room = new ModernRoom();
ModernCar car =new ModernCar();
如果出现修改

ClassicalRoom room = new ClassicalRoom();
ClassicalCar car =new ClassicalCar();

改为工厂模式以后
AbstractFactory factory =new ModernRoom();
AbstractRoom room =factory .CreatRoom();
AbstractCar car = factory .CreatCar();

而当房屋改了的情况
只需要修改
//只需要修改这个工厂就可以
AbstractFactory factory =new ClassicalFactory();
AbstractRoom room =factory .CreatRoom();
AbstractCar car = factory .CreatCar();
不知道我这样理解是否正确 希望指教
  回复  引用  查看    
#13楼 2008-06-30 15:48 | 水言木      
@小眼睛老鼠
对头...
如果是:ClassicalRoom room = factory.Create();
那一样还是依赖于实现,没有意义
  回复  引用  查看    
#14楼 2008-08-04 18:05 | 晓风残月      
@小眼睛老鼠
@水言木

我也是如此理解。

可能 Terry 这里没有在代码上体现出“面向接口编程”,反而带来了困惑

看看 idior 的注释应该更清晰点,http://idior.cnblogs.com/archive/2005/04/14/137913.html

标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  博客园首页

  新闻频道

  社区

  小组

  博问

  网摘

  闪存

  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2006-03-30 08:58 编辑过
成果网帮您增加网站收入


相关链接: