zoukankan      html  css  js  c++  java
  • 对象工厂设计模式

       

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

     

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

     

    对象工厂(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 });
  • 相关阅读:
    se 键盘鼠标操作事件
    警告框操作方法(alert弹窗)
    se自带截图方法
    CSS Selector 高级用法
    吃奶酪
    互不侵犯
    hdu1102
    P4744 Iron man
    玉米田
    状压dp题单
  • 原文地址:https://www.cnblogs.com/wanghui9072229/p/1976923.html
Copyright © 2011-2022 走看看