設計模式學習(十四)————抽象工廠模式(使用Qt框架的反射技術——根據字串動態建立類來實現)
抽象工廠模式:提供一個建立一系列相關或相互依賴物件的介面,而無需指定它們具體的類!
這個例子也可以用簡單工廠模式+反射+讀取配置檔案來完成,這樣更加簡潔!!!
普通的抽象工廠模式
下面通過一個模擬訪問資料庫的例子來進行說明:
#ifndef USER
#define USER
#include <QString>
#include <QtDebug>
class User
{
public:
User() = default;
int getID(){return _id;}
void setID(int id){_id = id;}
QString getName(){return _name;}
void setName(QString name){_name = name;}
private:
int _id;
QString _name;
};
class IUser
{
public:
virtual void Insert(User user) = 0;
virtual User* getUser(int id) = 0;
};
class SqlServerUser final: public IUser
{
public:
void Insert(User user) override
{
Q_UNUSED(user);
qDebug() <<"在SQL Server中給User表增加一條記錄" ;
}
User* getUser(int id) override
{
Q_UNUSED(id);
qDebug() <<"在SQL Server中根據id得到User表一條記錄";
return NULL; //這裡為了做例子,所以沒有真正返回業務需要的指標,只是返回空指標做示範
}
};
class AccessUser final: public IUser
{
public:
void Insert(User user) override
{
Q_UNUSED(user);
qDebug() <<"在Access中給User表增加一條記錄" ;
}
User* getUser(int id) override
{
Q_UNUSED(id);
qDebug() <<"在Access中根據id得到User表一條記錄";
return nullptr; //這裡為了做例子,所以沒有真正返回業務需要的指標,只是返回空指標做示範
}
};
class Department
{
private:
QString _departmentName;
};
class IDepartment
{
public:
virtual void Insert(Department department) = 0;
};
class SqlServerDepartment:public IDepartment
{
public:
void Insert(Department department) override
{
Q_UNUSED(department);
qDebug() <<"在SQL Server中給Department表增加一條記錄";
}
};
class AccessDepartment:public IDepartment
{
public:
void Insert(Department department) override
{
Q_UNUSED(department);
qDebug() <<"在Access中給Department表增加一條記錄";
}
};
class IFactory
{
public:
virtual IUser* CreateUser() = 0;
virtual IDepartment *CreateDepartment() = 0;
};
class SqlServerFactory: public IFactory
{
public:
IUser* CreateUser() override
{
return new SqlServerUser();
}
IDepartment* CreateDepartment() override
{
return new SqlServerDepartment();
}
};
class AccessFactory: public IFactory
{
public:
IUser* CreateUser() override
{
return new AccessUser();
}
IDepartment* CreateDepartment() override
{
return new AccessDepartment();
}
};
#endif // USER
#include "user.h"
#define use_SQLServer
int main(int argc, char *argv[])
{
Q_UNUSED(argc); Q_UNUSED(argv);
User *user = new User();
Department *department = new Department();
#ifdef use_SQLServer
IFactory *factory = new SqlServerFactory();
#else
IFactory *factory = new AccessFactory();
#endif
IUser *iu = factory->CreateUser();
IDepartment *id = factory->CreateDepartment();
id->Insert(*department);
iu->getUser(1);
iu->Insert(*user);
return 0;
}
這樣的實現就可以滿足同樣的程式碼,如果需要通過不同的資料庫實現同樣的功能的話,只需要在開頭的#define use_SQLServer
註釋掉和不註釋掉兩種方法即可,大大的增加的軟體的複用性,減少了相關的修改操作。
簡單工廠模式改進的抽象工廠模式
這裡進行抽象工廠模式的使用時,對於使用哪種資料庫來說,還是必須在主函式裡面通過新建不同的資料庫類來實現不同資料庫的訪問,如果我想要把這個邏輯也在邏輯程式碼中實現,就可以使用簡單工廠模式改進抽象工廠模式,程式碼如下:
#ifndef DATAACCESS_H
#define DATAACCESS_H
#include "user.h"
#include <QString>
class DataAccess
{
public:
static IUser* CreatUser()
{
IUser* result = nullptr;
if(_db == "Sqlserver")
result = new SqlServerUser();
else if(_db == "Access")
result = new AccessUser();
return result;
}
static IDepartment* CreatDepartment()
{
IDepartment* result = nullptr;
if(_db == "Sqlserver")
result = new SqlServerDepartment();
else if(_db == "Access")
result = new AccessDepartment();
return result;
}
private:
static const QString _db; //注意:靜態非整型資料成員,必須定義為const,且在類外初始化
};
//const QString DataAccess::_db = QString::fromUtf8("Sqlserver");
const QString DataAccess::_db = QString::fromUtf8("Access"); //把上面那句話取消註釋,本行加上註釋,就是使用sql服務的相關操作
這樣主函式改為:
#include "user.h"
#include "dataaccess.h"
int main(int argc, char *argv[])
{
Q_UNUSED(argc); Q_UNUSED(argv);
User *user = new User();
Department *department = new Department();
IUser *iu = DataAccess::CreatUser();
IDepartment *id = DataAccess::CreatDepartment();
id->Insert(*department);
iu->getUser(1);
iu->Insert(*user);
return 0;
}
這樣,在主函式中使用DataAccess
的簡單工廠來創建出對應的類的例項即可。然後如果需要改的資料庫的型別,直接在dataaccess.h
標頭檔案中將靜態變數_db
的內容改動一下即可。
在簡單工廠模式中使用反射技術來改進的抽象工廠模式
下面的內容是抽象工廠模式的第三重境界。前兩重分別是:普通的抽象工廠模式的使用、簡單工廠模式改進抽象工廠模式的使用。而第三重境界就是:在簡單工廠模式中使用反射技術來改進抽象工廠模式。話不多說,放上程式碼:
#ifndef DATAACCESS_H
#define DATAACCESS_H
#include "user.h"
Q_DECLARE_METATYPE(SqlServerUser)
Q_DECLARE_METATYPE(AccessUser)
Q_DECLARE_METATYPE(SqlServerDepartment)
Q_DECLARE_METATYPE(AccessDepartment)
void RegisterMetaType()
{
qRegisterMetaType<SqlServerUser>();
qRegisterMetaType<AccessUser>();
qRegisterMetaType<SqlServerDepartment>();
qRegisterMetaType<AccessDepartment>();
}
class DataAccess
{
public:
static IUser* CreatUser()
{
IUser* result = nullptr;
QString className = _db + "User";
int id = QMetaType::type(className.toLatin1());
if (id != QMetaType::UnknownType)
result = static_cast<IUser*>(QMetaType::create(id));
else
qDebug()<<"沒有在Qt中註冊成功該類";
return result;
}
static IDepartment* CreatDepartment()
{
IDepartment* result = nullptr;
QString className = _db + "Department";
int id = QMetaType::type(className.toLatin1());
if (id != QMetaType::UnknownType)
result = static_cast<IDepartment*>(QMetaType::create(id));
else
qDebug()<<"沒有在Qt中註冊成功該類";
return result;
}
private:
static const QString _db; //注意:靜態非整型資料成員,必須定義為const,且在類外初始化
};
#ifdef use_SQLServer
const QString DataAccess::_db = QString::fromUtf8("SqlServer");
#else
const QString DataAccess::_db = QString::fromUtf8("Access"); //把上面那句話取消註釋,本行加上註釋,就是使用sql服務的相關操作
#endif
#endif // DATAACCESS_H
由於我使用了Qt的反射技術,這裡講解一下。雖然C++本身不向java或者C#那樣可以有庫自帶反射實現的方法來實現通過類名動態的建立類,但是Qt框架的元系統可以使這一過程簡化,如果想要動態的在執行時根據字串的名字建立類,只需要完成以下三步即可:
第一步:將需要使用的類通過Q_DECLARE_METATYPE
註冊,如:Q_DECLARE_METATYPE(CExample)
。注意:(1)需要構造的類必須提供公用的建構函式、拷貝建構函式和解構函式,當然如果沒有複雜的需要深拷貝的資料,使用編譯器預設提供的也行。(2)以下類無需使用該巨集即可自動的註冊到Qt元系統中:
a.繼承自QObject的類;
b.QList<T>, QVector<T>, QQueue<T>, QStack<T>, QSet<T> 和 QLinkedList<T>
這些資料結構中T是 被註冊過的型別時;
c.QHash<T1, T2>, QMap<T1, T2> 和 QPair<T1, T2>
這些資料結構中T1和T2是 被註冊過的型別時;
d.QPointer<T>, QSharedPointer<T>, QWeakPointer<T>
, 這些智慧指標中T是被註冊過的型別時;
e.通過Q_ENUM
或者 Q_FLAG
註冊的列舉資料;
f.具有一個Q_GADGET
巨集的類。
第二步:如果需要動態的根據類名建立該類,需要在main函式中通過qRegisterMetaType
註冊,例如:qRegisterMetaType<CExample>();
注意:如果僅僅要在QVariant
中使用該類,就不需要這一步了。
第三步:通過int id = QMetaType::type(該類名字的字串轉換為QByteArray或者直接const char*)
,然後使用QMetaType::create(id)
返回一個新的該類的指標。例如:
QString className = "CExample";
int id = QMetaType::type(className.toLatin1()); //或者int id = QMetaType::type("CExample");
CExample* result = static_cast<CExample*>(QMetaType::create(id));
通過上面三步,就可以動態的通過類名建立類啦,我上面的DATAACCESS_H
檔案也是這樣用的,接下來,在main函式中的程式碼如下:
#define use_reflect //如果不使用反射,註釋掉這句話
#define use_SQLServer //如果使用AccessServer,註釋掉這句話
#include "user.h"
#include "dataaccess.h"
int main(int argc, char *argv[])
{
Q_UNUSED(argc); Q_UNUSED(argv);
#ifdef use_reflect
RegisterMetaType();
#endif
User *user = new User();
Department *department = new Department();
IUser *iu = DataAccess::CreatUser();
IDepartment *id = DataAccess::CreatDepartment();
id->Insert(*department);
iu->getUser(1);
iu->Insert(*user);
return 0;
}
這樣就實現了在簡單工廠模式中使用反射技術來改進抽象工廠模式。通過這樣的改進,我們可以發現當我們需要使用簡單工廠構造多個新的類的時候,只需要修改那個需要變化的字串,然後其他地方都不需要改動,就可以實現對不同資料庫的呼叫,真的是非常方便,非常好用,設計模式實在是太棒了!