对象工厂设计模式

   

如果你在你的某个系统中增加了一个子类,你要创建这个子类的对象,但又不想改变任何原有代码,有可能么?

 

答案是肯定的,用“对象工厂”设计模式。

 

对象工厂(Object Factory)是GoF 23种设计模式之外的模式,它既不是抽象工厂(Abstract Factory),也不是工厂方法(Factory Method),尽管可能跟它们有些渊源。我第一次看到介绍“对象工厂”的书是《C++设计新思维(Modern C++ Design)》,但我第一次看到对象工厂的代码,却比看到这书早,但我当时不知道它叫“对象工厂”。

 

C++设计新思维》(下载地址:http://d.download.csdn.net/down/2627586/pcevil/)第8章详细讲解了我们为什么会需要对象工厂,如何实现并泛化它等内容。本文并不想重复这些内容,而是想通过一个小例子,将使用对象工厂和不使用对象工厂的情况进行对比,来说明对象工厂会带来哪些好处。

 

         还是面向对象教科书上那个经典的Shape的例子。基于多态,你用C++编写了一套关于形状的系统,Shape是基类。可能你已经有了LineRectangle等子类。在你的客户程序里,通过传入形状的类型标识(假设我们用字符串来标识类型,当然用整型来标识也可以)来创建具体的(ConcreteShape。你的代码看起来可能是这样。

 

Shape  *  CreateShapeById( const  std:: string &  strShapeId)
{
    Shape 
*  pShape  =  NULL;

    
if  (strShapeId  ==   " Line " )
    {
        pShape 
=   new  Line;
    }
    
else   if  (strShapeId  ==   " Rectangle " )
    {
        pShape 
=   new  Rectange;
    }

    
return  pShape;
}

 

    这像是GoF《设计模式》里所说的参数化工厂方法。但这里违反了面向对象的最重要的规则:

 

1.       它基于型别标记执行了if-else语句(当用整型标识而换为switch语句时同理),这正是面向对象程序竭力消除的东西。

2.       它在一个源码文件中收集所有关于Shape子类的相关信息,这也是我们应该竭力避免的。客户代码文件都因此必须包含其头文件,造成编译依存性和维护上的瓶颈。

3.       它难以扩充。现在你需要增加Ellipse子类,如果没有使用对象工厂模式,除了增加Ellipse本身的代码,你至少还要增加以下代码:

 

a)       在你的客户代码文件里,增加

 

#include  " Ellipse.h "

 

b)       CreateShapeById中加入以下代码:

 

     else   if  (strShapeId  ==   " Ellipse " )
    {
        pShape 
=   new  Ellipse;
    }

  

c)       如果你用整型定义类型标识,你还要定义Ellipse形状的类型标识。比如:

 

#define  ELLIPSE 3

 

         现在让我们来改改,用对象工厂来实现。泛化的对象工厂的代码如下:

 

template
<
    
class  AbstractProduct,
    
class  IdentifierType,
    typename ProductCreator 
=  AbstractProduct *  ( * )()
>
class  Factory
{
private :
    Factory() {};
    Factory(Factory
&  factory);
    Factory
&   operator = ( const  Factory &  factory);

public :
    
bool  Register( const  IdentifierType &  id , ProductCreator creator)
    {
        associations_[id] 
=  creator;
        
return   true ;
    }

    
bool  Unregister( const  IdentifierType &  id)
    {
        
return  associations_.erase(id)  ==   1 ;
    }
  
    AbstractProduct 
*  CreateObject( const  IdentifierType &  id)
    {
        AssocMap::const_iterator i 
=  associations_.find(id);
        
if  (i  !=  associations_.end())
        {
            
return  (i -> second)();
        }
        
return  NULL;
    }

    
static  Factory *  Instance()
    {
        
static  Factory  *  pFactory  =  NULL;
        
if  ( ! pFactory)
        {
            
static  Factory factory;
            pFactory 
=   & factory;
        }
        
return  pFactory;
    }

private :
    typedef std::map
< IdentifierType, ProductCreator >  AssocMap;
    AssocMap associations_;
};

 

简单说说其工作机理。更详细的、深入的内容还是请看《C++设计新思维》。

 

1.       此对象工厂泛化了3样东西:

a)       抽象产品(Abstract product)。对应本例,就是Shape

b)       产品类型标识符(Product type identifier)。对应本例,我们用字符串标识,就是std::string

c)       产品生产者(Product creator)。对应本例,我们将用缺省的(也是最简单的)原型,也就是无参数、返回值为抽象产品指针的函数。

2.       它使用std::map作为产品类型标识符与产品生产者的映射的存储结构。

3.       Register负责向map中注册一个产品类型标识符与产品生产者的映射,Unregister则负责注销。

4.       CreateObject是对象工厂的核心,它会根据传入的产品类型标识符,找到对应的产品生产者,并调用它,创建出具体产品(Concrete Product)。

5.       Instance是实现了对象工厂的单件模式。这里用的是“Meyers Singleton”的一个变种。当然这里不是讨论Singleton的地方。

 

有了对象工厂,我们再在Shape.h里定义一个用来注册Shape具体类的模板类,这里有真正的形状的生产者(Create函数)。代码如下:

 

template  < class  DerivedShape >   class  RegisterShapeClass
{
public :
  
static  Shape  *  Create()
  {
    
return   new  DerivedShape;
  }
  RegisterShapeClass(
const  std:: string &  strShapeId)
  {
    Factory
< Shape, std:: string > ::Instance() -> Register(strShapeId, RegisterShapeClass::Create);
  }
};

 

再定义一个将类名转换为字符串的宏:

 

#define  ClassNameToString(x) #x

 

好了,有了对象工厂,CreateShapeById就变成这样:

 

Shape  *  CreateShapeById( const  std:: string &  strShapeId)
{
    
return  Factory < Shape, std:: string > ::Instance() -> CreateObject(strShapeId);
}

 

首先,这个函数短多了,而且不会随着子类的增加而膨胀,但这不是关键。这里面没有对具体Shape类型的引用。当我们需要增加Ellipse子类,只需在Ellipse类自己的代码里加上下面这句(向工厂注册自己),而不需要改变任何原有代码!

 

RegisterShapeClass < Ellipse >  RegisterEllipse(ClassNameToString(Ellipse));

 

这看起来有些奇异,但更奇异的是,不仅从原有代码中我们看不到任何引用新子类的代码,而且连Linker都会认为新子类没有被引用,而将新子类的obj排除在Link之外。当然,你也许会认为Link的时候使用/OPT:NOREF选项可以避免这个问题。但现实是, Visual C++(从VC6VC9)的/OPT:NOREF选项都有一个问题(参见http://social.msdn.microsoft.com/forums/en-US/vclanguage/thread/2aa2e1b7-6677-4986-99cc-62f463c94ef3):即使用此选项,仍然不能将新子类的obj文件Link进去。解决的办法也是在这个网址里看到的,加入类似下面这样一句,以使Linker强行将RegisterShapeClass<Ellipse>连接进去

 

#pragma  comment(linker, "/include:??0?$RegisterShapeClass@VEllipe@@@@QAE@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z")

 

在我曾开发过的医学图像处理系统中,需要用到对象工厂的地方,至少有3种:

 

1.       由用户输入类型,系统动态生成对应的对象实体。比如:用户选择不同的测量工具(Measurement,包括:DistanceAngle等),还有下达各种图像操作的命令(Command,比如:ZoomPanRotate等)。

2.       序列化。比如上条所说的Measurement,我们能够保存下来,并能够在某个时刻恢复(Restore)。保存时,用Measurement的名称来标识测量工具的类型并序列化,恢复时,根据这个类型标识动态生成对象,并反序列化。

3.       同步。我们称其为会议模式(Conference Mode),比如一个客户端上画出的Measurement,其它客户端上能同步看到,我们使用XML并进行流(std::stringstream)的输入和输出,来传输和同步数据和状态。当数据在其它客户端流入的时候,与反序列化相似,根据类型标识动态生成对象。

 

对象工厂使得客户不再需要(或较少)改变原有系统,但却很容易扩展系统。所以说:对象工厂是对OO开闭原则(OCP,对变更关闭,对扩展开放)非常好的阐释。

 

这里再多说一句:由于.Net的反射(Reflection)机制,使我们不用自己再去建造对象工厂就可以动态地生成对象。用C#写出来的代码应该类似于这样:

 

string  strShapeId  =   " Ellipse " ;
Type type 
=  Type.GetType(strShapeId);
Shape shape 
=  (Shape)Activator.CreateInstance(type,  new  Object[] {  this  });

 

转载于:https://www.cnblogs.com/wanghui9072229/archive/2011/03/08/1976923.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值