原文:http://www./node/350 在我繼續(xù)一系列的Qt數(shù)據(jù)序列化文章之前,有一個(gè)相對(duì)重要的需要提及的話題,那就是:基于類名動(dòng)態(tài)創(chuàng)建類對(duì)象的能力。 假定現(xiàn)在我們要?jiǎng)?chuàng)建一系列的形狀,形狀是一個(gè)抽象類,實(shí)際類是存儲(chǔ)在一個(gè)列表中的各種各樣的派生類:矩形、圓等等。在序列化期間,我們可以保存每一項(xiàng)的類 名和對(duì)象數(shù)據(jù),在反序列化(即加載數(shù)據(jù))時(shí),我們需要能夠創(chuàng)建合適類實(shí)例的能力,這就是要用到一個(gè)對(duì)象工廠的地方。在支持反射的語言中,例如C#、 Java,僅需要幾行代碼就可以從一個(gè)跟定的類名字符串獲得一個(gè)類實(shí)例。但是在c++中沒有這樣的機(jī)制。 一個(gè)簡單的解決方案是創(chuàng)建一個(gè)單獨(dú)的函數(shù),里面有一個(gè)大的switch塊(或者一系列的switch塊)來創(chuàng)建合適的類對(duì)象,盡管這種方法不雅觀并且破 壞了面向?qū)ο笤O(shè)計(jì),但是大多數(shù)情況下是可以接受的。然而,當(dāng)你有很多的類而且分散在應(yīng)用程序的不同模塊時(shí),使用上述方法可能會(huì)變得難以管理,而且當(dāng)應(yīng)用程 序有擴(kuò)展的模塊和動(dòng)態(tài)加載的插件時(shí),這就會(huì)變得更加困難。 另一個(gè)更優(yōu)雅的解決方法是有一個(gè)不需要知道任何對(duì)象類型的工廠,而要通過工廠實(shí)例化的類必須使用某種內(nèi)部圖來先注冊(cè),這樣,每個(gè)模塊或插件可以獨(dú)立的注冊(cè)它們各自類。 QT有兩種可以用來創(chuàng)建這樣的工廠的機(jī)制,它們看起來相似,實(shí)際上有很大的差別: QMetaType construct()方法能夠用來創(chuàng)建任何內(nèi)建類型的實(shí)例,或者是通過Q_DECLARE_METATYPE宏指定的自定義類型。這是 QVariant所要做的,用來內(nèi)部封裝自定義類型。然而這種機(jī)制是用來供變量類型使用的,也就是有默認(rèn)構(gòu)造和拷貝函數(shù)的類,但是對(duì)于抽象類對(duì)象是沒有意 義的,因?yàn)槌橄箢愅ǔJ褂弥羔槀鬟f,并且拷貝構(gòu)造通常被禁用。 QMetaObject newInstance()方法可以用來創(chuàng)建任何一個(gè)從QObject派生下來的類的實(shí)例,僅有的條件是類的構(gòu)造器必須通過Q_INVOKABLE修 飾,來明確地聲明。這和多態(tài)對(duì)象配合可以很好的工作,因?yàn)镼Object類通常作為各種抽象類的基類。值得注意的是,從QT4開始,若沒有額外的工作,僅 僅依靠類名是不可能檢索到QMetaObject的。
可以很容易的創(chuàng)建一個(gè)依賴于QMetaObject的對(duì)象工廠,這里有一種實(shí)現(xiàn),不過這種解決方法也有一些缺點(diǎn): 沒有在編譯期檢查是否存在合適的構(gòu)造函數(shù)可以訪問,或者參數(shù)類型是否正確,當(dāng)你實(shí)際嘗試創(chuàng)建實(shí)例時(shí),僅僅會(huì)得到一個(gè)運(yùn)行時(shí)警告,并返回空指針; 子類化QObject會(huì)增加每個(gè)對(duì)象實(shí)例的內(nèi)存占用,當(dāng)執(zhí)行運(yùn)行時(shí)類型檢查時(shí),通過QMetaObject進(jìn)行的動(dòng)態(tài)方法調(diào)用也會(huì)存在一些開銷。
然而,創(chuàng)建一個(gè)可以創(chuàng)建任何類的自定義類工廠也不是難事,下面是一個(gè)適用于任何繼承于QObject的類的創(chuàng)建工廠: class ObjectFactory { public: template<typename T> static void registerClass() { constructors().insert( T::staticMetaObject.className(), &constructorHelper<T> ); } static QObject* createObject( const QByteArray& className, QObject* parent = NULL ) { Constructor constructor = constructors().value( className ); if ( constructor == NULL ) return NULL; return (*constructor)( parent ); } private: typedef QObject* (*Constructor)( QObject* parent ); template<typename T> static QObject* constructorHelper( QObject* parent ) { return new T( parent ); } static QHash<QByteArray, Constructor>& constructors() { static QHash<QByteArray, Constructor> instance; return instance; } }; 使用這種途徑,不在需要使用Q_INVOKABLE聲明構(gòu)造器了,而且如果沒有找到合適的構(gòu)造器,只要這個(gè)類注冊(cè)了,在constructorHelper()方法中就會(huì)報(bào)告一個(gè)編譯錯(cuò)誤。而且代碼很容易使用: ObjectFactory::registerClass<Foo>(); // ... QObject* foo = ObjectFactory::createObject( "Foo" ); 同時(shí)也很容易修改這個(gè)代碼,來適用于那些不從QObject繼承的自定義抽象類,例如它可以使用任何傳遞給registerClass()方法或者自動(dòng)從 類的靜態(tài)成員接收的類型的“Key”,而不是使用從OMetaObject接收的類名作為“Key”.根據(jù)需要還有一組不同的參數(shù)可以傳遞給構(gòu)造函數(shù)。 |
|