每日一句英語學習,天天進步一點點:
羊哥以前寫一篇有趣的文章《答應我,別再if/else走天下了能夠嗎 | CodeSheep 》,羊哥在文中使用 Java 語言實現了枚舉類、工廠模式和策略模式的三種方式,來消除連環的 if / else
。內容層層遞進,由淺入深的方式我很是喜歡。程序員
看到有留言中有小夥伴想看 C++ 版本的,特此寫下了此文(已通過羊哥的贊成)。不過因爲 C++ 沒有枚舉類,因此本文不涉及此方式,但本文會帶你們一步一步的優化工廠模式和策略模式。編程
if / else
能夠說是咱們學習編程時,第一個學習的分支語句,簡單易理解,生活中也到處有的 if / else
例子:安全
老婆給當程序員的老公打電話:「下班順路買一斤包子帶回來,若是看到賣西瓜的,買一個。」
當晚,程序員老公手捧一個包子進了家門。。。 老婆怒道:「你怎麼就買了一個包子?!」 老公答曰:「由於看到了賣西瓜的。」bash
老婆的思惟:服務器
買一斤包子;
if( 看到賣西瓜的 )
買一隻( 西瓜 );
複製代碼
而程序員老公的程序:函數
if( ! 看見賣西瓜的 )
買一斤包子;
else
買一隻( 包子 );
複製代碼
很是生生動動的生活例子!若是身爲程序員的你,犯了一樣的思惟錯誤,別繼續問你媳婦爲何,問就是跪鍵盤:學習
進入本文正題。考慮如下栗子:通常來講咱們正常的後臺管理系統都有所謂的角色的概念,不一樣管理員權限不同,可以行使的操做也不同。優化
ROLE_ROOT_ADMIN
):有 A
操做權限ROLE_ORDER_ADMIN
):有 B
操做權限ROLE_NORMAL
):有 C
操做權限假設一個用戶進來,咱們須要根據不一樣用戶的角色來判斷其有哪些行爲。使用過多 if / else
連環寫法的咱們,確定下意識就以爲,這不簡單嘛,我上演一套連環的寫法:ui
class JudgeRole {
public:
std::string Judge( std::string roleName ) {
std::string result = "";
if( roleName == "ROLE_ROOT_ADMIN" ) // 系統管理員
{
result = roleName + "has A permission";
}
else if( roleName == "ROLE_ORDER_ADMIN" ) // 訂單管理員
{
result = roleName + "has B permission";
}
else if( roleName == "ROLE_NORMAL" ) // 普通用戶
{
result = roleName + "has C permission";
}
return result;
}
};
複製代碼
當系統裏有幾十個角色,那豈不是幾十個 if / else
嵌套,這個視覺效果絕對酸爽……這種實現方式很是的不優雅。spa
別人看了這種代碼確定大聲喊:「我X,哪一個水貨寫的!」
這時你聽到,千萬不要說:「那我改爲 switch / case
」。千萬別說,千萬別說哦,不然可能拎包回家了……
由於 switch / case
和 if / else
毛區別都沒,都是寫費勁、難閱讀、不易擴展的代碼。
接下來簡單講幾種改進方式,別再 if / else 走天下了。
不一樣的角色作不一樣的事情,很明顯就提供了使用工廠模式的契機,咱們只須要將不一樣狀況單獨定義好,並聚合到工廠裏面便可。
首先,定義一個公用接口 RoleOperation
,類裏有一個純虛函數 Op
,供派生類(子類)具體實現:
// 基類
class RoleOperation {
public:
virtual std::string Op() = 0; // 純虛函數
virtual ~RoleOperation() {} // 虛析構函數
};
複製代碼
接下來針對不一樣的角色類,繼承基類,並實現 Op 函數:
// 系統管理員(有 A 操做權限)
class RootAdminRole : public RoleOperation {
public:
RootAdminRole(const std::string &roleName)
: m_RoleName(roleName) {}
std::string Op() {
return m_RoleName + " has A permission";
}
private:
std::string m_RoleName;
};
// 訂單管理員(有 B 操做權限)
class OrderAdminRole : public RoleOperation {
public:
OrderAdminRole(const std::string &roleName)
: m_RoleName(roleName) {}
std::string Op() {
return m_RoleName + " has B permission";
}
private:
std::string m_RoleName;
};
// 普通用戶(有 C 操做權限)
class NormalRole : public RoleOperation {
public:
NormalRole(const std::string &roleName)
: m_RoleName(roleName) {}
std::string Op() {
return m_RoleName + " has C permission";
}
private:
std::string m_RoleName;
};
複製代碼
接下來在寫一個工廠類 RoleFactory
,提供兩個接口:
RegisterRole
成員函數GetRole
成員函數// 角色工廠
class RoleFactory {
public:
// 獲取工廠單例,工廠的實例是惟一的
static RoleFactory& Instance() {
static RoleFactory instance; // C++11 以上線程安全
return instance;
}
// 把指針對象註冊到工廠
void RegisterRole(const std::string& name, RoleOperation* registrar) {
m_RoleRegistry[name] = registrar;
}
// 根據名字name,獲取對應的角色指針對象
RoleOperation* GetRole(const std::string& name) {
std::map<std::string, RoleOperation*>::iterator it;
// 從map找到已經註冊過的角色,並返回角色指針對象
it = m_RoleRegistry.find(name);
if (it != m_RoleRegistry.end()) {
return it->second;
}
return nullptr; // 未註冊該角色,則返回空指針
}
private:
// 禁止外部構造和虛構
RoleFactory() {}
~RoleFactory() {}
// 禁止外部拷貝和賦值操做
RoleFactory(const RoleFactory &);
const RoleFactory &operator=(const RoleFactory &);
// 保存註冊過的角色,key:角色名稱 , value:角色指針對象
std::map<std::string, RoleOperation *> m_RoleRegistry;
};
複製代碼
把全部的角色註冊(聚合)到工廠裏,並封裝成角色初始化函數InitializeRole
:
void InitializeRole() // 初始化角色到工廠 {
static bool bInitialized = false;
if (bInitialized == false) {
// 註冊系統管理員
RoleFactory::Instance().RegisterRole("ROLE_ROOT_ADMIN", new RootAdminRole("ROLE_ROOT_ADMIN"));
// 註冊訂單管理員
RoleFactory::Instance().RegisterRole("ROLE_ORDER_ADMIN", new OrderAdminRole("ROLE_ORDER_ADMIN"));
// 註冊普通用戶
RoleFactory::Instance().RegisterRole("ROLE_NORMAL", new NormalRole("ROLE_NORMAL"));
bInitialized = true;
}
}
複製代碼
接下來藉助上面這個工廠,業務代碼調用只須要一行代碼,if / else
被消除的明明白白:
class JudgeRole {
public:
std::string Judge(const std::string &roleName) {
return RoleFactory::Instance().GetRole(roleName)->Op();
}
};
複製代碼
須要注意:在使用 Judge
時,要先調用初始化全部角色 InitializeRole
函數(能夠放在 main
函數開頭等):
int main() {
InitializeRole(); // 優先初始化全部角色到工廠
JudgeRole judgeRole;
std::cout << judgeRole.Judge("ROLE_ROOT_ADMIN") << std::endl;
std::cout << judgeRole.Judge("ROLE_ORDER_ADMIN") << std::endl;
std::cout << judgeRole.Judge("ROLE_NORMAL") << std::endl;
}
複製代碼
經過工廠模式實現的方式,想擴展條件也很容易,只須要增長新代碼,而不須要改動之前的業務代碼,很是符合「開閉原則」。
不知道小夥伴發現了沒有,上面實現工廠類,雖然看來去井井有理,可是當使用不當時會招致程序奔潰,那麼是什麼狀況會發生呢?
咱們先來分析上面的工廠類對外的兩個接口:
RegisterRole
註冊角色指針對象到工廠GetRole
從工廠獲取角色指針對象難道是指針對象沒有釋放致使資源泄露?不,不是這個問題,咱們也沒必要手動去釋放指針,由於上面的工廠是「單例模式」,它的生命週期是從第一次初始化後到程序結束,那麼程序結束後,操做系統天然就會回收工廠類裏的全部指針對象資源。
可是當咱們手動去釋放從工廠獲取的角色指針對象,那麼就會有問題了:
RoleOperation* pRoleOperation = RoleFactory::Instance().GetRole(roleName);
...
delete pRoleOperation; // 手動去釋放指針對象
複製代碼
若是咱們手動釋放了指針對象,也就致使工廠裏 map 中存放的指針對象指向了空,當下次再次使用時,就會招致程序奔潰!以下面的例子:
class JudgeRole {
public:
std::string Judge(const std::string &roleName) {
RoleOperation *pRoleOperation = RoleFactory::Instance().GetRole(roleName);
std::string ret = pRoleOperation->Op();
delete pRoleOperation; // 手動去釋放指針對象
return ret;
}
};
int main() {
InitializeRole(); // 優先初始化全部角色到工廠
JudgeRole judgeRole;
std::cout << judgeRole.Judge("ROLE_ROOT_ADMIN") << std::endl;
std::cout << judgeRole.Judge("ROLE_ROOT_ADMIN") << std::endl; // 錯誤!程序會奔潰退出!
return 0;
}
複製代碼
上面的代碼在使用第二次 ROLE_ROOT_ADMIN
角色指針對象時,就會招致程序奔潰,由於 ROLE_ROOT_ADMIN
角色指針對象已經在第一次使用完後,被手動釋放指針對象了,此時工廠 map 存放的就是空指針了。
能否優化呢?由於有的程序員是會手動釋放從工廠獲取的指針對象的。
上面的工廠類的缺陷就在於,new
初始化的指針對象只初始化了一次,若是手動 釋放了指針對象,就會致使此指針對象指向空,再次使用就會致使系統奔潰。
爲了改進這個問題,那麼咱們把 new
初始化方式放入工廠類獲取指針對象的成員函數裏,這也就每次調用該成員函數時,都是返回新 new
初始化過的指針對象,那麼這時外部就須要由手動釋放指針對象了。
下面的工廠類,改進了上面問題,同時採用模板技術,進一步對工廠類進行了封裝,使得不論是角色類,仍是其餘類,只要存在多態特性的類,均可以使用此工廠類,能夠說是「萬能」的工廠類了:
接下來把新的「萬能」工廠模板類,使用到本例的角色對象。
把角色註冊(聚合)到工廠的方式是構造 ProductRegistrar
對象 ,使用時需注意:
ProductType_t
指定的是基類(如本例 RoleOperation )ProductImpl_t
指定的是派生類(如本例 RootAdminRole、OrderAdminRole 和 NormalRole)咱們使用新的註冊(聚合)方式,對 InitializeRole
初始化角色函數改進下,參見下面:
void InitializeRole() // 初始化角色到工廠 {
static bool bInitialized = false;
if (bInitialized == false) {
// 註冊系統管理員
static ProductRegistrar<RoleOperation, RootAdminRole> rootRegistrar("ROLE_ROOT_ADMIN");
// 註冊訂單管理員
static ProductRegistrar<RoleOperation, OrderAdminRole> orderRegistrar("ROLE_ORDER_ADMIN");
// 註冊普通用戶
static ProductRegistrar<RoleOperation, NormalRole> normalRegistrar("ROLE_NORMAL");
bInitialized = true;
}
}
複製代碼
從工廠獲取角色指針對象的函數是 GetProduct
,需注意的是:
delete
資源。咱們使用新的獲取角色對象的方式,對 Judge
函數改進下,參見下面:
class JudgeRole {
public:
std::string Judge(const std::string &roleName) {
ProductFactory<RoleOperation>& factory = ProductFactory<RoleOperation>::Instance();
// 從工廠獲取對應的指針對象
RoleOperation *pRoleOperation = factory.GetProduct(roleName);
// 調用角色的對應操做權限
std::string result = pRoleOperation->Op();
// 手動釋放資源
delete pRoleOperation;
return result;
}
};
複製代碼
唔,每次都手動釋放資源這種事情,會很容易遺漏。若是咱們遺漏了,就會招致了內存泄漏。爲了不此機率事情的發生,咱們用上「智能指針],讓它幫咱們管理吧:
class JudgeRole {
public:
std::string Judge(const std::string &roleName) {
ProductFactory<RoleOperation>& factory = ProductFactory<RoleOperation>::Instance();
std::shared_ptr<RoleOperation> pRoleOperation(factory.GetProduct(roleName));
return pRoleOperation->Op();
}
};
複製代碼
採用了 std::shared_ptr
引用計數智能指針,咱們不在須要時刻記住要手動釋放資源的事情啦(咱們一般都會忘記……),該智能指針會在當引用次數爲 0 時,自動會釋放掉指針資源。
來,咱們接着來,除了工廠模式,策略模式也不妨試一試
策略模式和工廠模式寫起來其實區別也不大!策略模式也採用了面向對象的繼承和多態機制。
在上面工廠模式代碼的基礎上,按照策略模式的指導思想,咱們也來建立一個所謂的策略上下文類,這裏命名爲 RoleContext
:
class RoleContext {
public:
RoleContext(RoleOperation *operation) : m_pOperation(operation) {
}
~RoleContext() {
if (m_pOperation) {
delete m_pOperation;
}
}
std::string execute() {
return m_pOperation->Op();
}
private:
// 禁止外部拷貝和賦值操做
RoleContext(const RoleContext &);
const RoleContext &operator=(const RoleContext &);
RoleOperation *m_pOperation;
};
複製代碼
很明顯上面傳入的參數 operation
就是表示不一樣的「策略」。咱們在業務代碼裏傳入不一樣的角色,便可獲得不一樣的操做結果:
class JudgeRole {
public:
std::string Judge(RoleOperation *pOperation) {
RoleContext roleContext(pOperation);
return roleContext.execute();
}
};
int main() {
JudgeRole judgeRole;
std::cout << judgeRole.Judge(new RootAdminRole("ROLE_ROOT_ADMIN")) << std::endl;
std::cout << judgeRole.Judge(new OrderAdminRole("ROLE_ORDER_ADMIN")) << std::endl;
std::cout << judgeRole.Judge(new NormalRole("ROLE_NORMAL")) << std::endl;
return 0;
}
複製代碼
固然,上面策略類還能夠進一步優化:
// 策略類模板
// 模板參數 ProductType_t,表示的是基類
template <class ProductType_t> class ProductContext {
public:
ProductContext(ProductType_t *operation)
: m_pOperation(operation) {
}
~ProductContext() {
if (m_pOperation) {
delete m_pOperation;
}
}
std::string execute() {
return m_pOperation->Op();
}
private:
// 禁止外部拷貝和賦值操做
ProductContext(const ProductContext &);
const ProductContext &operator=(const ProductContext &);
ProductType_t* m_pOperation;
};
複製代碼
使用方式,沒太大差異,只須要指定類模板參數是基類(如本例 RoleOperation
) 便可:
class JudgeRole {
public:
std::string Judge(RoleOperation *pOperation) {
ProductContext<RoleOperation> roleContext(pOperation);
return roleContext.execute();
}
};
複製代碼
C++ 和 Java 語言都是面向對象編程的方式,因此都是能夠經過面向對象和多態特性下降代碼的耦合性,同時也可以使得代碼易擴展。因此對於寫代碼事情,不要着急下手,先思考是否有更簡單、更好的方式去實現。
C++ 之父 Bjarne Stroustrup 曾經說起過程序員的三大美德是懶惰、急躁、傲慢,其中之一的懶惰這個品質,就是告知咱們要花大力氣去思考,避免消耗過多的精力個體力(如敲代碼)。
如有錯誤或者不當之處,可在本公衆號內反饋,一塊兒學習交流!
關注公衆號,後臺回覆「我要學習」,便可免費獲取精心整理「服務器 Linux C/C++ 」成長路程(書籍資料 + 思惟導圖)