享受代码,享受人生

SOA is an integration solution. SOA is message oriented first.
The Key character of SOA is loosely coupled. SOA is enriched
by creating composite apps.
posts - 207, comments - 2288, trackbacks - 129, articles - 44
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

你了解创建者模式了吗? --- 创建者模式详解

Posted on 2005-04-14 23:36 idior 阅读(6932) 评论(30)  编辑 收藏 所属分类: Design

你了解创建者模式了吗?

 

我准备从How Why 这两个角度来谈谈我对创建者模式的理解.

 

How to implement Factory and Abstract Factory

 

对于创建者模式, 有一点很容易被大多数人所忽视所误解, 就是Factory模式和Abstract Factory模式的区别.

他们的最重要的区别不是在于Abstract Factory是用于创建产品族, Factory仅仅用于创建一种类型的产品.

他们的区别在于一个很重要的思想: 是采用聚合还是继承? Favor Composition than inheritance.

谈到创建者模式大家不自然的就会想到他们的任务都是用于创建对象. 然而真是这样吗? 他们仅仅是用于创建对象?

让我们来看看Gof书中有关Factory的一个例子


注意到了吗
? Application就是一个Factory, 对于不同的Document我们需要扩展不同的Application然后override它们的CreateDocument方法. 然而Application仅仅是创建文档对象吗? 它还做了一系列的工作,将新建的文档对象添加到文档列表并打开文档. 并且还提供了一个完全和创建无关的打开文档(OpenDocument方法)的功能. 看看它和你想象中的创建者模式一样吗? 它可不是仅仅new 一个对象那么简单. 你注意到了吗? 我估计很多人都没有注意到这点, 甚至在吕震宇的文章中.(吕兄可别怪我 :P 谁叫你太有名了)

 
吕震宇文中的例图

我们只看到了
factory方法. 然而在Gof中的图是这样的:


注意到那个
AnOperation() 了吗?

 

再来看看GofFactory模式的评价:

There are two common ways to parameterize a system by the classes of objects it creates. One way is to subclass the class that creates the objects; this corresponds to using the Factory Method  pattern. The main drawback of this approach is that it can require creating a new subclass just to change the class of the product. Such changes can cascade. For example, when the product creator is itself created by a factory method, then you have to override its creator as well.

 

因为Factory模式不仅仅是创建一个对象那么简单,通常还要加上对创建出来的对象做一些操作(比如初始化一些对象的一些属性, 把对象和环境关联起来)  这样就算那些操作不变仅仅是因为创建的对象变了,你就需要新建一个Factory.

 

For example, when the product creator is itself created by a factory method, then you have to override its creator as well.

这句话有点让人费解,让我们把这句话的演示一下:



Gof
的意思大概如图所示.就是说仅仅是因为文档变了我们不仅要为它造一个老爸(MyApp),还要连带为它造一个爷爷(MyAppCreator).怎样才能不造爷爷?使用Abstract Factory.

这时的工厂就完全是为了创建一个新的对象, 而不在工厂中对新建的对象做任何操作.如下图所示:

 

AppCreator appc=new AppCreator(); 

Application app
=appCreator.Creat(new MyDocCreator()); 

app.NewDoc(); 

将创建对象的功能(DocCreator)从对象的其他操作中(Application)完全分离出来作为一个类, 这样我们就不需要为每种类型的文档造爷爷了.

这个是什么模式?

看看Gof的原话

The other way to parameterize a system relies more on object composition: Define an object that's responsible for knowing the class of the product objects, and make it a parameter of the system. This is a key aspect of the Abstract Factory, Builder, and Prototype  patterns. All three involve creating a new "factory object" whose responsibility is to create product objects.

 

可以发现Factory Vs Abstract Factory  à   Inheritance Vs Composition

 

最后再来谈谈Why?

 

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? 你明白了吗?

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

 

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

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

Feedback

#1楼    回复  引用    

2005-04-15 08:41 by ttyp [未注册用户]
没有争论哪有进步,只要就事论事,不要人身攻击,争论是团队达成一致不是很好么,不争论带这问题不代表没有问题

#2楼    回复  引用    

2005-04-15 09:18 by idior [未注册用户]
我从来不搞人生攻击, 可就是得罪了很多人. 我实在无法做到慢条细理的跟别人讨论.

#3楼    回复  引用  查看    

2005-04-15 09:33 by lichdr      
是自己感觉得罪人还是真的把人得罪了是要分开的。
其实有时人家也不会很介意。
常常跟人家“吵”了一架,然后过会就没事了。

#4楼    回复  引用  查看    

2005-04-15 10:00 by 冰火      
创建模式得作用:

1、封装创建逻辑,就像idior说得决不仅仅new一个对象;

2、封装创建逻辑变化,客户代码不用修改,或仅少量修改;

另外,IoC/DI模式,数据驱动的设计,反射技术的运用,创建模式已经有了一些变化,Factory Method 模式似乎已过时。

个人观点!

#5楼 [楼主]   回复  引用  查看    

2005-04-15 10:04 by idior      
@冰火 ##IoC/DI模式,数据驱动的设计,反射技术的运用
确实创建者模式在有了元数据的支持下发生了一些改进.
不过通常的小应用中还是可以使用的Factory模式的.
而大的应用就可以使用到Spring框架,对对象的创建做全局的管理.
BTW你概括的很好. 这两点尤其是第一点常被人所忽视.

#6楼    回复  引用  查看    

2005-08-23 10:04 by 吕震宇      
非常好!看了两遍

1、造爷爷的例子非常好,说出了Facotry method模式的一个致命缺点。

2、Absctract factory模式的图画的很好,就图中的两处“注释”我想补充一下我的想法:

a、从图中我们可以看出Ioc的影子。在右侧注释中,有一命令return new Application(dc),dc是DocCreator类型,言外之意可以是任何子类型,所以实现了构造函数的注入。这样,汽车工厂的例子可以映射成:CarFactory cf = new CarFactory(BMWCreator());

b、在图中间的注释中我们除了看到create方法被调用外,还应注意有两条命令:docs.Add(doc);以及doc.Open();,前一条命令是典型的“缓冲”方式,将创建的对象存入docs中,将来再创建时可以克隆一个出来(Prototype模式)

而doc.Open();多少让我有点迷惑,是初始化吗?作用是什么?另外NewDoc()的返回值类型是void?希望能和idior探讨一下。

3、另外我对于哪个“AnOperation()”方法的解释:“通常还要加上对创建出来的对象做一些操作(比如初始化一些对象的一些属性, 把对象和环境关联起来) ”感觉不是特别赞同。如果和本文第一张图相对比的话,可以明显感觉出第三张图的AnOperation是和第一张图的OpenDocument相对应的。那么问题就是OpenDocument是干什么的。我是这么理解的:

如果注意第一张图中NewDocument方法最后一条命令是Doc->Open(),并且可以从最后一张图中看到NewDocument函数返回值为void,因此我们可以推断此NewDocument方法的目的不是return一个Doc,而是调用Doc的Open方法。如果这样的话,就不难理解AnOperation方法的意思了。其实命令Docs.Add(doc)是将创建出来的对象缓存起来,下次就不用再调用NewDocument方法了,而是调用OpenDocument方法(见本文第一张图),由第一张图和第三张图的映射关系,我们也可以推断此处的AnOperation是调用缓冲的对象而不需再创建对象。

BTW,这是我的一个猜测,也希望能和idior探讨一下。

#7楼 [楼主]   回复  引用  查看    

2005-08-23 15:00 by idior      
这里的NewDoc的含义你可以把它想象成word菜单里的New命令。
Open就是激活当前新建的文档。
Add就是Word中的MDI功能。

---
Application就是一个Factory, 对于不同的Document我们需要扩展不同的Application然后override它们的CreateDocument方法. 然而Application仅仅是创建文档对象吗? 它还做了一系列的工作,将新建的文档对象添加到文档列表并打开文档. 并且还提供了一个完全和创建无关的打开文档(OpenDocument方法)的功能. 看看它和你想象中的创建者模式一样吗? 它可不是仅仅new 一个对象那么简单. 你注意到了吗?
---

#8楼    回复  引用  查看    

2005-08-23 20:15 by 吕震宇      
看来是我理解错了,我误解了ADD和OPEN,原来是MDI的概念。

“Application就是一个Factory, 对于不同的Document我们需要扩展不同的Application然后override它们的CreateDocument方法”这是Factory method模式,一个Document对应一个Application。

而文中最后一个图是Abstract Factory,这时候只要一个Application就够了。

#9楼    回复  引用    

2006-02-10 14:52 by maketech [未注册用户]
using System;
using System.Collections.Generic;
using System.Text;

namespace Creator
{

public abstract class factory
{
public abstract car creator();
}

public class benzfactory:factory
{
public override car creator()
{
car c = new benz();
c.brand = "benz";
return c;
}
}

public class bmwfactory:factory
{
public override car creator()
{
car c = new bmw();
c.brand = "bmw";
return c;
}
}

public class car
{
public string brand;
}

public class benz:car
{
public benz()
{
Console.WriteLine("benz has created");
//brand = "benz";
}
}

public class bmw:car
{
public bmw()
{
Console.WriteLine("bmw has created");
// brand = "bmw";

}

}

}

这段代码是否领悟了你的精神,还望指教?qi_gu@263.net

#10楼 [楼主]   回复  引用  查看    

2006-02-10 21:38 by idior      
你能否简单描述一下你的想法,你是不是认为把brand的设置 移到factory中代表了什么?

#11楼    回复  引用  查看    

2006-03-17 10:47 by 韦恩卑鄙      
我想我通过学习这片文章渐渐清晰了创建者模式的WHY 解除了我学习HOW的时候的迷茫. 因为接触过一些OFFICE宏操作 和APPLOCATION这里例子共鸣很强

感谢idior

#12楼    回复  引用    

2006-04-24 22:08 by 学习.NET1 [未注册用户]
======================
CarFactory carFac=new BenzeFactory();

Car car=carFac.Factory();

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

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

Ok? 你明白了吗?

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

按照我的理解, 要做到 " 当你需要换Car的时候你只需修改一处代码就是CarFactory carFac=new XXXFactory(); " ,则carFac作为当前客户代码类的字段。
此时设想将car作为当前客户代码类的字段(类级别的变量), 当需要换Car时,也是只需修改一处代码呀,即Car car=new XXXCar(); 如果是这样,则没看到有啥好处。

其实, 应该强调的是用Car car=new XXXCar(); 创建一个car时,可能还需要一些附属代码,来完成car的一些初始化等修饰工作,不如将这些修饰工作放到一个独立的类(工厂类)中, 客户代码调用时更简单,。

如果创建一个car时,仅仅用Car car=new XXXCar(); 就可以,不需要修饰工作,则此时应用工厂类就显得过度设计了。

我感觉工厂模式的好处在于:
1、代码写起来更elegant, 且易于阅读。
2、更易于扩展,如果有这个需要的话。 若需求变化了,创建一个IProduct, 还需要对其进行一些附加处理才能使用,则这是用工厂模式则易于扩展。
3、对于抽象工厂模式而言,工厂类就更有存在的价值了。
4、如果语言没有提供反射功能,切换产品时,客户代码必须要改至少一个地方。如果想 一点也不改 是办不到的,除非使用反射,这就类似于provider模式了。

#13楼    回复  引用    

2006-04-25 08:16 by idior, [未注册用户]
@学习.NET1
Car car=new XXXCar();
想想看你能在所有的地方都用同一辆car吗?

#14楼    回复  引用  查看    

2006-04-25 12:37 by 学习.NET      
但是:
如果换车的话,CarFactory carFac=new XXXFactory(); 是不是也要改变?

我觉得问题的关键,不是使用工厂模式之后对客户代码修改减少了,这只是带来的附加好处,关键在于职责更加清晰,特别是IProduct, 还需要对其进行一些附加处理时。

#15楼    回复  引用  查看    

2006-04-25 12:44 by 学习.NET      
“如果创建一个car时,仅仅用Car car=new XXXCar(); 就可以,不需要修饰工作,则此时应用工厂类就显得过度设计了。”
可能没表述清楚,我的本意是
如果创建一个car时,不需要修饰工作,仅仅用Car car=new XXXCar() 就可以,则此时还要应用工厂类就显得过度设计了。 不过话又说回来,现在创建时不需要修饰工作,谁又能保证将来创建时也不需要修饰工作呢?呵呵。

#16楼 [楼主]   回复  引用  查看    

2006-04-25 13:47 by idior      
@学习.NET
我说的是你能在所有的地方都用同一辆car的实例吗?

其实概括起来就是两句话
1、封装创建逻辑,决不仅仅new一个对象;

2、封装创建逻辑变化,客户代码不用修改,或仅少量修改;

#17楼    回复  引用  查看    

2006-05-07 07:43 by 学习.NET      
谢谢!

如果不是在所有地方都用同一辆car,不管是用
Car car=new XXXCar(); 还是用
CarFactory carFac=new XXXFactory();
只要换车,必须更换这两句(不用反射时)。

当创建car的逻辑不仅仅是Car car=new XXXCar()就能解决问题时,就有必要将 创建逻辑的职责 从客户代码中分离出来,拿到一个单独的类中,从而在创建逻辑发生变化时,客户代码仅仅修改CarFactory carFac=new XXXFactory()。
你上面讲时,用的是当创建逻辑简化到了Car car=new XXXCar()的情况,所以用不用CarFactory 给人的感觉是一样的,呵呵,我钻了个空子而已,你总结的非常好,受教了 :)。

#18楼 [楼主]   回复  引用  查看    

2006-05-07 11:32 by idior      
不客气,通过讨论让问题更加明确。

#19楼    回复  引用    

2006-05-10 17:28 by LEGION [未注册用户]
请问:工厂方法和构造器方法有什么不同,看了GOF的书,关于这两种方法很迷惑,看不出有什么大的不同啊,请指教,谢谢
GOF关于Factory Method的示例
class MazeGame{
public:
Maze* CreateMaze();
//Factory methods
virtual Maze* Makemaze() const;
virtual Room* MakeRoom() const;
virtual Wall* MakeWall() const;
virtual Door* MakeDoor() const;
}
GOF关于Builder的示例
class MazeBuilder{
public:
virtual Maze* Makemaze() const;
virtual Room* MakeRoom() const;
virtual Wall* MakeWall() const;
virtual Door* MakeDoor() const;
protected:
MazeBuilder();

}

#20楼 [楼主]   回复  引用  查看    

2006-05-10 17:47 by idior      
提醒你两点
1. 你看看factory 和builder模式中分别是谁负责creat maze的。

2. 看看buildRoom和makeroom有什么不同。

最重要是第一点。

#21楼    回复  引用    

2006-05-11 15:16 by LEGION [未注册用户]
关于第一点:builder模式可以通过其本身的类成员直接create maze的,而factory模式里面也有缺省的create maze的实现,最简单的就是都可以写为
virtual Maze *MakeMaze() const
{return new Maze;}
关于第二点:两个成员函数都可以写的一样啊,如下
virtual Maze *MakeRoom() const
{return new Room;}

能不能明示一下呢,谢谢!!!

#22楼    回复  引用    

2006-05-11 15:17 by LEGION [未注册用户]
关于第二点:两个成员函数都可以写的一样啊,如下
virtual Room *MakeRoom() const
{return new Room;}

#23楼 [楼主]   回复  引用  查看    

2006-05-12 15:32 by idior      
@LEGION
我的理解中builder不直接创建对象,而是由director来使用builder构造对象然后通过get得到构造好的对象。

#24楼    回复  引用  查看    

2006-11-08 13:34 by 立冬[匿名]      
看看它和你想象中的创建者模式一样吗? 它可不是仅仅new 一个对象那么简单.
-----
我认为工厂模式这样的创建者模式就是仅用于创建对像的,对于其它操作那不是工厂模式应该干的,如果类包含了其他操作,那这个类要么不应该称之为工厂类,要么应该分离出来。

你上面演示的图中,Application其实已经是Client的,它使用document对象。如果要用工厂模式来解决以后可能新增文档的问题,应该在它增加工厂类

#25楼    回复  引用    

2007-01-08 18:33 by 青岛开和 [未注册用户]
也许我对设计模式一知半解,所以看了你的文章心存疑虑。
------
Gof的意思大概如图所示.就是说仅仅是因为文档变了我们不仅要为它造一个老爸(MyApp),还要连带为它造一个爷爷(MyAppCreator).
------
我不明白这里为什么需要造爷爷?
如果把document作为产品,Application作为工厂,MyAppCreator表示了什么?

#26楼    回复  引用  查看    

2008-02-23 18:21 by @高超      
正在看中介者模式..........

#27楼    回复  引用  查看    

2008-07-10 14:05 by gotolovo      
设想一下在我们的代码中散布了无数这样的代码.不止一处(这点很重要)
那么当你以后需要换车的时候, 是不是需要一一修改我们的创建代码把Benze改成BMW.

然后我们再用工厂来实现一下:
Car car=benzeFactory().Factory();
呵呵 这算什么? 没事找事做. 如果要换车,你不是还要修改原来的代码改成下面这样.
Car car=bmwFactory().Factory();
是吗?
如果创建代码只有这里一处可能是这样, 但是如果很多地方都要创建的话就不是了.
当你需要换Car的时候你只需修改一处代码就是CarFactory carFac=new XXXFactory();
其他创建车的地方,永远不变,还是Car car=carFac.Factory();
对于楼主的这里还是有些不清楚的地方啊
Car car=new Benze(); 与
CarFactory carFac=new BenzeFactory();
Car car=carFac.Factory();
的作用相同啊.就是car变化的时候.都需要修改,也都是修改了一处


首先创建了一辆奔驰.
Car car=new Benze();
突然我们的车变了, 变成宝马了. Ok 我修改一下.
Car car=new BMW();
设想一下在我们的代码中散布了无数这样的代码.不止一处(这点很重要)
这里 car出现的次数多少应该是没有关系的吧.出现多少次car 就有多少个new BMW();
然而工厂模式不也是嘛?
CarFactory carFac=new BenzeFactory();
Car car=carFac.Factory();
难道只是 Car car=carFac.Factory();
而没有 CarFactory carFac=new BenzeFactory();
还是看不出工厂模式的所以然啊

#28楼    回复  引用  查看    

2008-08-04 17:49 by 晓风残月      
@gotolovo
我猜测这里 idior 的 carFac 变量是一个“全局性的变量”,整个应用程序中创建某种特定相关应用场景的Car 都访问这个 carFac 的 CreateCar() 方法

因此你只需要将
CarFactory carFac = new BMWFactory();
修改成
CarFactory carFac = new BenzeFactory();

其他引用 carFac.CreateCar() 的地方是不变的!

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