每日一句英语学习,天天进步一点点:
- "Without purpose, the days would have ended, as such days always end, in disintegration."
- 「少了目标,一天仍是会结束,它老是以支离破碎的形式结束。」
羊哥以前写一篇有趣的文章《答应我,别再if/else走天下了能够吗 | CodeSheep 》,在文中使用 Java 语言实现了枚举类、工厂模式和策略模式的三种方式,来消除连环的 if / else
。内容层层递进,由浅入深的方式我很是喜欢。程序员
看到有留言中有小伙伴想看 C++ 版本的,特此写下了此文(已通过羊哥的赞成)。不过因为 C++ 没有枚举类,因此本文不涉及此方式,但本文会带你们一步一步的优化工厂模式和策略模式。编程
if / else
能够说是咱们学习编程时,第一个学习的分支语句,简单易理解,生活中也到处有的 if / else
例子:安全
老婆给当程序员的老公打电话:“下班顺路买一斤包子带回来,若是看到卖西瓜的,买一个。”
当晚,程序员老公手捧一个包子进了家门。。。
老婆怒道:“你怎么就买了一个包子?!”
老公答曰:“由于看到了卖西瓜的。” 服务器
老婆的思惟:app
买一斤包子; if( 看到卖西瓜的 ) 买一只( 西瓜 );
而程序员老公的程序:函数
if( ! 看见卖西瓜的 ) 买一斤包子; else 买一只( 包子 );
很是生生动动的生活例子!若是身为程序员的你,犯了一样的思惟错误,别继续问你媳妇为何,问就是跪键盘:学习
进入本文正题。考虑如下栗子:通常来讲咱们正常的后台管理系统都有所谓的角色的概念,不一样管理员权限不同,可以行使的操做也不同。优化
ROLE_ROOT_ADMIN
):有 A
操做权限ROLE_ORDER_ADMIN
):有 B
操做权限ROLE_NORMAL
):有 C
操做权限假设一个用户进来,咱们须要根据不一样用户的角色来判断其有哪些行为。使用过多 if / else
连环写法的咱们,确定下意识就以为,这不简单嘛,我上演一套连环的写法:spa
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
嵌套,这个视觉效果绝对酸爽……这种实现方式很是的不优雅。操作系统
别人看了这种代码确定大声喊:“我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
初始化过的指针对象,那么这时外部就须要由手动释放指针对象了。
下面的工厂类,改进了上面问题,同时采用模板技术,进一步对工厂类进行了封装,使得不论是角色类,仍是其余类,只要存在多态特性的类,均可以使用此工厂类,能够说是「万能」的工厂类了:
接下来把新的「万能」工厂模板类,使用到本例的角色对象。
1. 把角色注册(聚合)到工厂的方式是构造 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; } }
2. 从工厂获取角色指针对象的函数是 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++ 」成长路程(书籍资料 + 思惟导图)