• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            eryar

            PipeCAD - Plant Piping Design Software.
            RvmTranslator - Translate AVEVA RVM to OBJ, glTF, etc.
            posts - 603, comments - 590, trackbacks - 0, articles - 0

            工廠方法Factory Methods

            Posted on 2013-03-22 21:20 eryar 閱讀(5587) 評論(4)  編輯 收藏 引用

            Factory Methods

            工廠方法

            eryar@163.com

            摘要Abstract:本文主要是對《API Design for C++》中Factory Methods章節(jié)的翻譯,若有不當(dāng)之處,歡迎指正。

            關(guān)鍵字Key Words:C++、Factory Pattern、

            一、概述 Overview

            工廠方法是創(chuàng)建型模式,允許在不指定需要創(chuàng)建對象類型的情況下創(chuàng)建出對象。本質(zhì)上來說,工廠方法就是一個通用的構(gòu)造函數(shù)。C++中的構(gòu)造函數(shù)有以下幾種限制:

            l 無返回值(No return result)。在構(gòu)造函數(shù)中不能返回一個值。這就意味著:例如當(dāng)構(gòu)造失敗時不能返回一個NULL作為初始化失敗的信號。

            l 命名有約束(Constrained naming)。構(gòu)造函數(shù)還是很好識別的,因為它的命名必須與類名一樣。

            l 靜態(tài)綁定創(chuàng)建的(Statically bound creation)。當(dāng)創(chuàng)建一個對象時,必須指定一個在編譯時就能確定的類名。如:Foo *f = new Foo(),F(xiàn)oo就是編譯器必須知道的類名。C++的構(gòu)造函數(shù)沒有運行時的動態(tài)綁定功能(dynamic binding at run time)。

            l 無虛構(gòu)造函數(shù)(No virtual constructors)。在C++中不能聲明虛的構(gòu)造函數(shù),必須指定在編譯時能確定的類型。編譯器據(jù)此為指定的類型分配內(nèi)存,然后調(diào)用基類的默認(rèn)構(gòu)造函數(shù),再調(diào)用指定類的構(gòu)造函數(shù)。這就是不能定義構(gòu)造函數(shù)為虛函數(shù)的原因。

            相反地,工廠方法(factory methods)突破了以上所有的限制。工廠方法的基本功能就是一個可以返回一個類的實例的簡單函數(shù)。但是,它通常與繼承組合使用,派生的類可以重載工廠方法以返回派生類的實例。使用抽象基類(Abstract Base Classes)來實現(xiàn)工廠很常見,也很有用。

            二、抽象基類 Abstract Base Classes

            抽象基類就是包含一個或多個純虛函數(shù)(pure virtual methods)的類,這樣的類不是具體類且不能用new來實例化。相反地,它是作為其它派生類的基類,由派生類來具體實現(xiàn)那些純虛函數(shù)。例如:

            #ifndef RENDERER_H
            #define RENDERER_H

            #include 
            <string>

            ///
            /// An abstract interface for a 3D renderer.
            ///
            class IRenderer
            {
            public:
                
            virtual ~IRenderer() {}
                
            virtual bool LoadScene(const std::string &filename) = 0;
                
            virtual void SetViewportSize(int w, int h) = 0;
                
            virtual void SetCameraPos(double x, double y, double z) = 0;
                
            virtual void SetLookAt(double x, double y, double z) = 0;
                
            virtual void Render() = 0;
            };

            #endif

            上述代碼定義了一個抽象基類,描述了一個相當(dāng)簡單的3D圖形渲染器(renderer)。函數(shù)的后綴“=0”聲明這個函數(shù)是純虛函數(shù),表示這個函數(shù)必須由其派生類來具體實現(xiàn)。

            抽象基類是描述了多個類共有的行為的抽象單元,它約定了所有具體派生類必須遵守的合同。在Java中,抽象基類也叫接口(interface),只是Java的接口只能是公用的方法(public method),靜態(tài)變量,并且不能定義構(gòu)造函數(shù)。將類名IRenderer帶上“I”就是為了表明這個類是接口類(interface class)。

            當(dāng)然,抽象基類中并不是所有的方法都必須是純虛函數(shù),也可以實現(xiàn)一些函數(shù)。

            當(dāng)任意一個類有一個或多個虛函數(shù)時,通常會把抽象基類的析構(gòu)函數(shù)聲明為虛函數(shù)。如下代碼說明了這樣做的重要性:

            class IRenderer
            {
                
            // no virtual destructor declared
                virtual void Render() = 0;
            };

            class RayTracer : public IRenderer
            {
                RayTracer();
                
            ~RayTracer();
                
            void Render(); // provide implementation for ABC method
            };

            int main(intchar **)
            {
                IRenderer 
            *= new RayTracer();
                
            // delete calls IRenderer::~IRenderer, not RayTracer::~RayTracer
                delete r;
            }

             

            三、簡單工廠模式 Simple Factory Example

            在復(fù)習(xí)了抽象基類后,讓我們在簡單工廠方法中使用它。繼續(xù)以renderer.h為例,聲明創(chuàng)建工廠,創(chuàng)建的對象類型為IRenderer,代碼如下所示:

            #ifndef RENDERERFACTORY_H
            #define RENDERERFACTORY_H

            #include 
            "renderer.h"
            #include 
            <string>

            ///
            /// A factory object that creates instances of different
            /// 3D renderers.
            ///
            class RendererFactory
            {
            public:
                
            /// Create a new instance of a named 3D renderer.
                
            /// type can be one of "opengl", "directx", or "mesa"
                IRenderer *CreateRenderer(const std::string &type);
            };

            #endif

            這里只聲明了一個工廠方法:它只是一個普通的函數(shù),返回值是對象的實例。注意到這個方法不能返回一個指定類型的IRender實例,因為抽象基類是不能被實例化的。但是它可以返回派生類的實例。當(dāng)然,你可以使用字符串作為參數(shù)來指定需要創(chuàng)建對象的類型。

            假設(shè)已經(jīng)實現(xiàn)了派生自IRender的三個具體類:IRenderer::OpenGLRenderer,DirectXRenderer、MesaRenderer。再假設(shè)你不想讓使用API的用戶知道可以創(chuàng)建哪些類型:他們必須完全隱藏在API后面。基于這些條件,可以實現(xiàn)工廠方法的程序如下:

            // rendererfactory.cpp
            #include "rendererfactory.h"
            #include 
            "openglrenderer.h"
            #include 
            "directxrenderer.h"
            #include 
            "mesarenderer.h"

            IRenderer 
            *RendererFactory::CreateRenderer(const std::string &type)
            {
                
            if (type == "opengl")
                    
            return new OpenGLRenderer;

                
            if (type == "directx")
                    
            return new DirectXRenderer;

                
            if (type == "mesa")
                    
            return new MesaRenderer;

                
            return NULL;
            }

            這個工廠方法可以返回IRenderer的三個派生類之一的實例,取決于傳入的參數(shù)字符串。這就可以讓用戶決定在運行時而不是在編譯時創(chuàng)建哪個派生類,這與普通的構(gòu)造函數(shù)要求一致。這樣做是有很多好處的,因為它可以根據(jù)用戶輸入或根據(jù)運行時讀入的配置文件內(nèi)容來創(chuàng)建不同的對象。

            另外,注意到實現(xiàn)具體派生類的頭文件只在rendererfactory.cpp中被包含。它們不出現(xiàn)在rendererfactory.h這個公開的頭文件中。實際上,這些頭文件是私有的頭文件,且不需要與API一起發(fā)布的。這樣用戶就看不到不同的渲染器的私有細節(jié),也看不到具體可以創(chuàng)建哪些不同的渲染器。用戶只需要通過字符串變量來指定種要創(chuàng)建的渲染器(若你愿意,也可用一個枚舉來區(qū)分類型)。

            此例演示了一個完全可接受的工廠方法。但是,其潛在的缺點就是包含了對可用的各派生類的硬編碼。若系統(tǒng)需要添加一個新的渲染器,你必須再編輯rendererfactory.cpp。這并不會讓人很煩,重要的是不會影響你提供的公用的API。但是,他的確不能在運行時添加支持的新的派生類。再專業(yè)點,這意味著你的用戶不能向系統(tǒng)中添加新的渲染器。通過擴展的對象工廠來解決這些問題。

            四、擴展工廠模式 Extensible Factory Example

            為了讓工派生類從工廠方法中解耦,且允許在運行時添加新的派生類,可以去維護包含類型及與類型創(chuàng)建關(guān)聯(lián)的函數(shù)的映射(map)來更新一下工廠類。可以通過添加幾個新的函數(shù)用來注冊與注銷新的派生類。在運行時能注冊新的類允許這種類型的工廠方法模式可用于創(chuàng)建可擴展的接口。

            還有個需要注意的事是工廠對象必須保存狀態(tài),即最好只有一個工廠對象。這也是工廠對象通常是單件的(singletons)。為了程序的簡單明了,這里使用靜態(tài)變量為例。將所有要點都考慮進來,新的工廠對象代碼如下所示:

            #ifndef RENDERERFACTORY_H
            #define RENDERERFACTORY_H

            #include 
            "renderer.h"
            #include 
            <string>
            #include 
            <map>
            ///
            /// A factory object that creates instances of different
            /// 3D renderers. New renderers can be dynamically added
            /// and removed from the factory object.
            ///
            class RendererFactory
            {
            public:
                
            /// The type for the callback that creates an IRenderer instance
                typedef IRenderer *(*CreateCallback)();

                
            /// Add a new 3D renderer to the system
                static void RegisterRenderer(const std::string &type,
                                             CreateCallback cb);
                
            /// Remove an existing 3D renderer from the system
                static void UnregisterRenderer(const std::string &type);

                
            /// Create an instance of a named 3D renderer
                static IRenderer *CreateRenderer(const std::string &type);

            private:
                typedef std::map
            <std::string, CreateCallback> CallbackMap;
                
            static CallbackMap mRenderers;
            };

            #endif

            為了程序的完整性,將其.cpp文件中的代碼示例如下:

            #include "rendererfactory.h"
            #include 
            <iostream>

            // instantiate the static variable in RendererFactory
            RendererFactory::CallbackMap RendererFactory::mRenderers;

            void RendererFactory::RegisterRenderer(const std::string &type,
                                                   CreateCallback cb)
            {
                mRenderers[type] 
            = cb;
            }

            void RendererFactory::UnregisterRenderer(const std::string &type)
            {
                mRenderers.erase(type);
            }

            IRenderer 
            *RendererFactory::CreateRenderer(const std::string &type)
            {
                CallbackMap::iterator it 
            = mRenderers.find(type);
                
            if (it != mRenderers.end())
                {
                    
            // call the creation callback to construct this derived type
                    return (it->second)();
                }

                
            return NULL;
            }

            使用工廠對象創(chuàng)建派生類的方法如下所示:

            #include "rendererfactory.h"
            #include 
            <iostream>

            using std::cout;
            using std::endl;

            /// An OpenGL-based 3D renderer
            class OpenGLRenderer : public IRenderer
            {
            public:
                
            ~OpenGLRenderer() {}
                
            bool LoadScene(const std::string &filename) { return true; }
                
            void SetViewportSize(int w, int h) {}
                
            void SetCameraPos(double x, double y, double z) {}
                
            void SetLookAt(double x, double y, double z) {}
                
            void Render() { cout << "OpenGL Render" << endl; }
                
            static IRenderer *Create() { return new OpenGLRenderer; }
            };

            /// A DirectX-based 3D renderer
            class DirectXRenderer : public IRenderer
            {
            public:
                
            bool LoadScene(const std::string &filename) { return true; }
                
            void SetViewportSize(int w, int h) {}
                
            void SetCameraPos(double x, double y, double z) {}
                
            void SetLookAt(double x, double y, double z) {}
                
            void Render() { cout << "DirectX Render" << endl; }
                
            static IRenderer *Create() { return new DirectXRenderer; }
            };

            /// A Mesa-based software 3D renderer
            class MesaRenderer : public IRenderer
            {
            public:
                
            bool LoadScene(const std::string &filename) { return true; }
                
            void SetViewportSize(int w, int h) {}
                
            void SetCameraPos(double x, double y, double z) {}
                
            void SetLookAt(double x, double y, double z) {}
                
            void Render() { cout << "Mesa Render" << endl; }
                
            static IRenderer *Create() { return new MesaRenderer; }
            };


            int main(intchar **)
            {
                
            // register the various 3D renderers with the factory object
                RendererFactory::RegisterRenderer("opengl", OpenGLRenderer::Create);
                RendererFactory::RegisterRenderer(
            "directx", DirectXRenderer::Create);
                RendererFactory::RegisterRenderer(
            "mesa", MesaRenderer::Create);

                
            // create an OpenGL renderer
                IRenderer *ogl = RendererFactory::CreateRenderer("opengl");
                ogl
            ->Render();
                delete ogl;

                
            // create a Mesa software renderer
                IRenderer *mesa = RendererFactory::CreateRenderer("mesa");
                mesa
            ->Render();
                delete mesa;

                
            // unregister the Mesa renderer
                RendererFactory::UnregisterRenderer("mesa");
                mesa 
            = RendererFactory::CreateRenderer("mesa");
                
            if (! mesa)
                {
                    cout 
            << "Mesa renderer unregistered" << endl;
                }

                
            return 0;
            }

            你的API的用戶可以在系統(tǒng)中注冊與注銷一個新的渲染器。編譯器將會確保用戶定義的新的渲染器必須實現(xiàn)抽象基類IRenderer的所有抽象接口,即新的渲染器類必須實現(xiàn)抽象基類IRenderer所有的純虛函數(shù)。如下代碼演示了用戶如何自定義新的渲染器,在工廠對象中注冊,并叫工廠對象為之創(chuàng)建一個實例:

            這里需要注意的一點是我向類UserRenderer中添加了一個Create()函數(shù),這是因為工廠對象的注冊方法需要返回一個對象的回調(diào)函數(shù)。這個回調(diào)函數(shù)不一定必須是抽象基類IRenderer的一部分,它可以是一個自由的函數(shù)。但是向抽象基類IRenderer中添加這個函數(shù)是一個好習(xí)慣,這樣就確保了所有相關(guān)功能的一致性。實際上,為了強調(diào)這種約定,可以將Create作為抽象基類IRenderer的一個純虛函數(shù)。

            五、結(jié)論 Conclusion

            Finally, I note that in the extensible factory example given here, a renderer callback has to be

            visible to the RegisterRenderer() function at run time. However, this doesn’t mean that you

            have to expose the built-in renderers of your API. These can still be hidden either by registering

            them within your API initialization routine or by using a hybrid of the simple factory and the extensible

            factory, whereby the factory method first checks the type string against a few built-in names.

            If none of those match, it then checks for any names that have been registered by the user. This hybrid

            approach has the potentially desirable behavior that users cannot override your built-in classes.

             

            PDF Version: Factory Method

            Feedback

            # re: 工廠方法Factory Methods[未登錄]  回復(fù)  更多評論   

            2014-08-15 14:11 by lcalqf
            寫的非常好

            # re: 工廠方法Factory Methods  回復(fù)  更多評論   

            2014-08-15 14:23 by eryar
            不是我寫的,
            是對《API Design for C++》中Factory Methods章節(jié)的翻譯。
            @lcalqf

            # re: 工廠方法Factory Methods  回復(fù)  更多評論   

            2015-01-20 23:55 by douzi
            實際上,為了強調(diào)這種約定,可以將Create作為抽象基類IRenderer的一個純虛函數(shù)。
            請問,這一句的方法,如何實現(xiàn)?
            子類實現(xiàn)純虛函數(shù)如何注冊為回調(diào)?

            # re: 工廠方法Factory Methods  回復(fù)  更多評論   

            2015-01-21 18:05 by eryar
            @douzi
            Hi douzi,

            你好!

            作為抽象基類的純虛函數(shù)即為實現(xiàn)方法。因為基類的純虛函數(shù),所有派生的子類中必須要重新實現(xiàn),否則編譯錯誤。這就是一種利用編譯器的約束。

            子類實現(xiàn)的純虛函數(shù)的注冊方法與現(xiàn)在的方式一樣。

            Best Regards,
            Shing Liu

            只有注冊用戶登錄后才能發(fā)表評論。
            網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問   Chat2DB   管理


            国产三级精品久久| 久久精品这里热有精品| 88久久精品无码一区二区毛片 | 亚洲人成网站999久久久综合| 久久影院综合精品| 久久AV高潮AV无码AV| 无码精品久久一区二区三区 | 国产精品久久久久天天影视| 中文字幕久久精品无码| 久久久久亚洲AV片无码下载蜜桃 | 亚洲精品高清国产一久久| 97久久精品国产精品青草| AV狠狠色丁香婷婷综合久久| 久久精品国产网红主播| 99久久精品国产麻豆| 色综合久久综精品| 国内精品久久久久久久影视麻豆| 亚洲精品国产成人99久久| 国产精品狼人久久久久影院| 国产精品日韩深夜福利久久| 国产精品无码久久久久 | 亚洲国产天堂久久综合| 久久精品国产男包| 国产成人精品综合久久久久| 日韩人妻无码精品久久久不卡 | 伊人色综合久久天天人守人婷 | 一级做a爰片久久毛片人呢| 精品多毛少妇人妻AV免费久久| 久久国产精品波多野结衣AV| 综合久久精品色| 浪潮AV色综合久久天堂| 国产亚洲美女精品久久久久狼| 久久这里只有精品首页| 亚洲国产成人久久综合区| 亚洲精品国精品久久99热一| 久久99国产精品久久99果冻传媒| 99久久伊人精品综合观看| 狠狠色丁香久久婷婷综合蜜芽五月 | 久久精品这里热有精品| 亚洲欧美久久久久9999| 久久精品中文騷妇女内射|