通用工厂类Factory(C++实现)

来源:互联网 发布:b站小学生知乎 编辑:程序博客网 时间:2024/06/05 17:12
灵光一闪:
        抽象工厂模式经常被使用,那么同时也会多次写相同或类似的代码。能不能设计一个通用的工厂类,以避免重复设计工厂类呢?


        先回忆一下基础的工厂类设计。
 class Button        {        };        class LinuxButton : public Button        {        };        class WindowsButton: public Button        {        };        class AbstractFactory        {         public:            Button* create Button() = 0;        };        class LinuxConcreteFactory : public AbstractFactory        {         public:            Button* createButton()            {                return new LinuxButton ;            }        };                class WindowsConcreteFactory : public AbstractFactory        {        pulbic:            Base *createBase()            {                return new WindowsButton;            }        };


        以上是一个基本的抽象工厂类。这套代码会根据不同的操作实现不同的控件,比如运行在linux下,那么会要产生一个Linux格式的Button,运行在Windows下,那么会产生Windows的Button。很多用过工厂类的猿们应该都有写过这样的工厂。但是这样的工厂有很大的问题:
  1. 有新产品类时,要修改AbstractFactory的接口,同时要改变所有具体工厂去实现新的创建对象函数。比如,现在要加一个Label的控件,那么会有Label、LinuxLabel和WindowsLabel类。这时就要去修改AbstractFactory的接口,然后所有实现类都要去实现新的函数。
  2. 要记住每个类创建对象的对应函数,当然,名字取得得当,这不是问题,就像上面的例子一样,对于类Button,创建一个createButton的函数。
  3. 当要使用这个工厂创建对象的类非常多时,这时工厂的接口就非常大。
  4. 当具体工厂的个数比较多时,这样实现具体工厂就更加麻烦。比如,这时要加上Mac系统的控件,此时就得去新建一个MacConcreteFactory的类,并去继承AbstractFactory类,然后实现所以函数。

        那么有没有比较简便点的方法呢?

=============================================================================
        下面给出一个《C++ API设计》这本书的一个工厂类的设计。
        Button* createLinuxButton()        {            return new LinuxButton;        }        Button *createWindowsButton()        {            return new WindowsButton;        }        class ButtonFactoryEx        {        public:                void registerClass(const string &name, Button* (*callback)())                {                    cm[name] = callback;                }                Button* create(const string &name)                {                    CreateMap::iterator it = cm.find(name);                    if (it != cm.end())                    {                        return (*(it->second))();                    }                    else                    {                        return NULL;                    }                }                // ...省略一些其他不用介绍的函数,可以参考《C++ API设计》        private:                typedef Button* (*CreateCallback)();                typedef map<string, CreateCallback> CreateMaps;                CreateMaps cm;        };


        使用这个拓展的工厂,就不用再使用抽象工厂,然后对其子类化,再实现各个函数了。不过引用了每一个类的生产函数,这些函数可以放入到类中做static函数。
        现在要使用Linux类的Button,使用Windows类的Button,只要将生产的回调函数注册进去(以下称为注册类到工厂)。如:
        ButtonFactoryEx factory;        factory.registerClass("linux", createLinuxButton);        factory.registerClass("windows", createWindowsButton);        factory.create("linux");  // 产生Linux的button        factory.create("windows"); // 产生windows的button

        这样设计符合了开闭原则。现在如果要加入Mac格式的Button,只要写好MacButton后,创建一个createMacButton的函数,然后将它注册到工厂里。已经不用更改工厂类的接口了。
        但是,这样设计又带来了一些新的问题:
  1.  对于不同的产品类,要使用不同的工厂,使工厂类个数膨胀。比如,现在要加一个Label的类,那么就要弄一个LabelFactoryEx的工厂。
  2.  如果传了没有注册进行的字符串去创建对象,或者拼错字符串了,比如"linux"拼成了"linus",那么将会返回NULL。这要运行时才能知道错误,而不是编译期。
  3.  在运行期要先注册类到工厂。
  4.  要管理多个不同的工厂对象。这是一个比较严重的问题。如果没管理好,在其他的模块中将访问不了这个对象,那么这个工厂就没有用了。
        这样设计其实是将产品的对象创建函数移到了工厂之外,然后将该函数注册到工厂里面。

        对于以上问题,可以对所有的产品类创建一个基类Object,然后将工厂的回调函数设计成返回Object*,然后用户再使用dynamic_cast。这样处理可以解决上面的第1和第4个问题,因为要管理的工厂对象减少到一个了,这样也方便处理。而要实现基础工厂那个,根据不同的环境产生不同的工厂,只要产生不同的工厂对象就行了。但是,这样做却会引入这样的问题:
  1. 所有的产品类要继承Object。如果这个产品类已经写好了,或者这个产品类不是你写的,你只有这个类的库,没有源代码,不能改接口,那怎么办?即使可以修改这个类的设计,但是所有的产品都继承Object将会很繁琐。修改已经存在的类还可能会引起已存在的代码产生新的问题。
  2. 是使用dynamic_cast会使系统的运行效率严重降低。

下页开始设计这篇文章要讲解的工厂。
============================================================================================
        还有一种替换的方案是使用模板类或者模板函数。
        将工厂类设计成模板的,这样就可以解决实现多个工厂的问题。但是,还有第4个问题还解决不了,只是工厂类的实现由程序员交给了编译器去处理了。读者可以自己去实现,以区别。
        
        如果将工厂类不设计成模块的,而将里面的方法设计成模板的,这样一个工厂对象就可以接收不同的产品的创建回调函数。只要在使用的时候将产品的类型传到模板函数里就行了。
        下面是这个工厂的初步设计。
        class Factory        {        public:            template <typename ReturnType>            bool registerClass(const string &name, ReturnType* (*createCallback)());                        template <typename ReturnType>            ReturnType* produce(const string &name);                // ...        };


        这里使用registerClass去将一个产品的创建回调函数注册进去,使用produce去生产对象。
        这时将引起另外的一个问题,那就是对于不同产品的回调函数,怎么去储存?因为类的数据成员不能因为模板函数的参数不同而加入不同的数据成员。也就是说不能使用数据成员存在回调函数了。

        那能不能用成员函数去处理呢?答案是:可以的。下面是我的设计:
class Factory        {        public:            // 接上面的类设计        private:            enum HandleType {GET, ADD};            template <typename ReturnType>            bool callback_container(const string, ReturnType* (**createCallback), HandleType type)            {                typedef ReturnType* (*Callback)();                typedef map<string, Callback> CallbackMap;                static CallbackMap cm;            }        };

        使用callback_container的模板函数来处理不同类型产品的存在问题。当有不同的产品注册进来时,编译器将会在编译期产生不同的重载函数。每个函数里面又有一个static的成员记录回调函数。因为static的成员变量只会被初始化一次,且在内存中一直存在。所以这样的设计是可以行得通的。因为要对同一个产品类型都用同一个callback_container来处理,那么就要加入操作标志的耦合,以决定是要处理取回调函数操作还是增加新的回调函数操作。

        现在要处理注册和回调,只要这样操作。
       Factory linuxFactory;        linuxFactory.registerClass<Button>("button", createLinuxButton);        linuxFactory.registerClass<Label>("label", createLinuxLabel);   // 假设设计了这个类和回调函数        Factory windowsFactory;        windowsFactory.registerClass<Button>("button", createWindowsButton);        windowsFactory.registerClass<Label>("label", createWindowsLabel);   // 假设设计了这个类和回调函数        // 产生Linux的button        linuxFactory.produce<Button>("button");        // 产生Linux的Label        linuxFactory.produce<Label>("label");


        这样就解决了使用Object基类设计的问题。

        现在还有问题有:
  1. 运行期要先注册回调函数到工厂。
  2. 不存在的名字或拼错的名字去创建。
        这两个问题到目前为止,C++是解决不了的。
        还有一个问题,就是像上面的linuxFactory和windowsFactory能不能再进行管理。因为一个程序中使用的工厂应该是有限的,而且是很少的。特别是设计成这样通用的工厂,那应该只有一个对象了。当然,这是可以解决的,通过单例模式封装工厂。
static Factory& Factory::instance(const string &name);


        使用不同的名字处理工厂对象,不同的工厂对象对应一个名字,使用容器放起来。下次再通过名字可以找出该工厂。这样也方便在其他模块中使用工厂,而不用使用全局变量。当然,这样处理后,那么对于callback_container的处理就更复杂了。因为不同的工厂对象里面,它们不能使用相同的callback_container里面的cm。所以就要再加一层窗口去存在不同对象的cm了。完整代码参照我的github。这样处理后,可以将callback_container设成static的。

        使用单例对象,上面的使用就可以这样使用了。
         #define LINUX_FACTORY Factory::instance("linux")        #define WIN_FACTORY Factory::instance("windows")        LINUX_FACTORY.registerClass<Button>("button", createLinuxButton);        LINUX_FACTORY.registerClass<Label>("label", createLinuxButton);        WIN_FACTORY.registerClass<Button>("button", createWindowsButton);        WIN_FACTORY.registerClass<Label>("label", createWindowsLabel);        LINUX_FACTORY.produce<Button>("button");        LINUX_FACTORY.produce<Label>("label");


        对于上面所设计的代码,剩下的问题是:
  1. 运行期要先注册回调函数到工厂。
  2. 不存在的名字或拼错的名字去创建。
        当然,目前是还解决不了的。

        在代码设计方面,对于callback_container的函数太过于复杂。与一个函数只处理一个逻辑相违。但是,目前我只有这样的实现方法,如果读者有更好的解决方案,欢迎分享。


转载请注明出处。http://blog.csdn.net/tenghui0425/article/details/23838535

0 0