C篇答应我,别再ifelse走天

由于C++没有枚举类,所以本文不涉及此方式,但本文会带大家一步一步的优化工厂和策略模式。

万能工厂

每日一句英语学习,每天进步一点点:

Withoutpurpose,thedayswouldhaveended,assuchdaysalwaysend,indisintegration.

「少了目标,一天还是会结束,它总是以支离破碎的形式结束。」

正文

糟糕if/else连环

if/else可以说是我们学习编程时,第一个学习的分支语句,简单易理解,生活中也处处有的

if/else例子:

老婆给当程序员的老公打“下班顺路买一斤包子带回来,如果看到卖西瓜的,买一个。”当晚,程序员老公手捧一个包子进了家门。。。老婆怒道:“你怎么就买了一个包子?!”老公答曰:“因为看到了卖西瓜的。”

老婆的思维:

买一斤包子;

if(看到卖西瓜的)

买一只(西瓜);

而程序员老公的程序:

if(!看见卖西瓜的)

买一斤包子;

else

买一只(包子);

非常生生动动的生活例子!如果身为程序员的你,犯了同样的思维错误,别继续问你媳妇为什么,问就是跪键盘:

进入本文正题。考虑以下栗子:一般来说我们正常的后台管理系统都有所谓的角色的概念,不同管理员权限不一样,能够行使的操作也不一样。

系统管理员(ROLE_ROOT_ADMIN):有A操作权限

订单管理员(ROLE_ORDER_ADMIN):有B操作权限

普通用户(ROLE_NORMAL):有C操作权限

假设一个用户进来,我们需要根据不同用户的角色来判断其有哪些行为。使用过多if/else连环写法的我们,肯定下意识就觉得,这不简单嘛,我上演一套连环的写法:

当系统里有几十个角色,那岂不是几十个if/else嵌套,这个视觉效果绝对酸爽……这种实现方式非常的不优雅。

别人看了这种代码肯定大声喊:“我X,哪个水货写的!”

这时你听到,千万不要说:“那我改成switch/case”。千万别说,千万别说哦,否则可能拎包回家了……

因为switch/case和if/else毛区别都没,都是写费劲、难阅读、不易扩展的代码。

接下来简单讲几种改进方式,别再if/else走天下了。

工厂模式——它不香吗?

不同的角色做不同的事情,很明显就提供了使用工厂模式的契机,我们只需要将不同情况单独定义好,并聚合到工厂里面即可。

首先,定义一个公用接口RoleOperation,类里有一个纯虚函数Op,供派生类(子类)具体实现:

接下来针对不同的角色类,继承基类,并实现Op函数:

接下来在写一个工厂类RoleFactory,提供两个接口:

用以注册角色指针对象到工厂的RegisterRole成员函数;

用以获取对应角色指针对象的GetRole成员函数。

把所有的角色注册(聚合)到工厂里,并封装成角色初始化函InitializeRole:

voidInitializeRole()//初始化角色到工厂

{

staticboolbInitialized=false;

if(bInitialized==false){

//注册系统管理员

RoleFactory::Instance().RegisterRole(ROLE_ROOT_ADMIN,newRootAdminRole(ROLE_ROOT_ADMIN));

//注册订单管理员

RoleFactory::Instance().RegisterRole(ROLE_ORDER_ADMIN,newOrderAdminRole(ROLE_ORDER_ADMIN));

//注册普通用户

RoleFactory::Instance().RegisterRole(ROLE_NORMAL,newNormalRole(ROLE_NORMAL));

bInitialized=true;

}

}

接下来借助上面这个工厂,业务代码调用只需要一行代码,if/else被消除的明明白白:

需要注意:在使用Judge时,要先调用初始化所有角色InitializeRole函数(可以放在main函数开头等):

通过工厂模式实现的方式,想扩展条件也很容易,只需要增加新代码,而不需要改动以前的业务代码,非常符合「开闭原则」。

不知道小伙伴发现了没有,上面实现工厂类,虽然看来去井然有序,但是当使用不当时会招致程序奔溃,那么是什么情况会发生呢?

我们先来分析上面的工厂类对外的两个接口:

RegisterRole注册角色指针对象到工厂

GetRole从工厂获取角色指针对象

难道是指针对象没有释放导致资源泄露?不,不是这个问题,我们也不必手动去释放指针,因为上面的工厂是「单例模式」,它的生命周期是从第一次初始化后到程序结束,那么程序结束后,操作系统自然就会回收工厂类里的所有指针对象资源。

但是当我们手动去释放从工厂获取的角色指针对象,那么就会有问题了:

如果我们手动释放了指针对象,也就导致工厂里map中存放的指针对象指向了空,当下次再次使用时,就会招致程序奔溃!如下面的例子:

classJudgeRole{public:std::stringJudge(conststd::stringroleName){RoleOperation*pRoleOperation=RoleFactory::Instance().GetRole(roleName);std::stringret=pRoleOperation-Op();deletepRoleOperation;//手动去释放指针对象returnret;}};intmain(){InitializeRole();//优先初始化所有角色到工厂JudgeRolejudgeRole;std::coutjudgeRole.Judge(ROLE_ROOT_ADMIN)std::endl;std::coutjudgeRole.Judge(ROLE_ROOT_ADMIN)std::endl;//错误!程序会奔溃退出!return0;}

上面的代码在使用第二次ROLE_ROOT_ADMIN角色指针对象时,就会招致程序奔溃,因为ROLE_ROOT_ADMIN角色指针对象已经在第一次使用完后,被手动释放指针对象了,此时工厂map存放的就是空指针了。

可否优化呢?因为有的程序员是会手动释放从工厂获取的指针对象的。

上面的工厂类的缺陷就在于,new初始化的指针对象只初始化了一次,如果手动释放了指针对象,就会导致此指针对象指向空,再次使用就会导致系统奔溃。

为了改进这个问题,那么我们把new初始化方式放入工厂类获取指针对象的成员函数里,这也就每次调用该成员函数时,都是返回新new初始化过的指针对象,那么这时外部就需要由手动释放指针对象了。

下面的工厂类,改进了上面问题,同时采用模板技术,进一步对工厂类进行了封装,使得不管是角色类,还是其他类,只要存在多态特性的类,都可以使用此工厂类,可以说是「万能」的工厂类了:

接下来把新的「万能」工厂模板类,使用到本例的角色对象。

1.把角色注册(聚合)到工厂的方式是构造ProductRegistrar对象,使用时需注意:

模板参数ProductType_t指定的是基类(如本例RoleOperation)

模板参数ProductImpl_t指定的是派生类(如本例RootAdminRole、OrderAdminRole和NormalRole)

我们使用新的注册(聚合)方式,对InitializeRole初始化角色函数改进下,参见下面:

voidInitializeRole()//初始化角色到工厂{staticboolbInitialized=false;if(bInitialized==false){//注册系统管理员staticProductRegistrar

rootRegistrar(ROLE_ROOT_ADMIN);//注册订单管理员staticProductRegistrar

orderRegistrar(ROLE_ORDER_ADMIN);//注册普通用户staticProductRegistrar

normalRegistrar(ROLE_NORMAL);bInitialized=true;}}

2.从工厂获取角色指针对象的函数是GetProduct,需注意的是:

使用完角色指针对象后,需手动delete资源。

我们使用新的获取角色对象的方式,对Judge函数改进下,参见下面:

classJudgeRole{public:std::stringJudge(conststd::stringroleName){ProductFactory

factory=ProductFactory

::Instance();//从工厂获取对应的指针对象RoleOperation*pRoleOperation=factory.GetProduct(roleName);//调用角色的对应操作权限std::stringresult=pRoleOperation-Op();//手动释放资源deletepRoleOperation;returnresult;}};

唔,每次都手动释放资源这种事情,会很容易遗漏。如果我们遗漏了,就会招致了内存泄漏。为了避免此概率事情的发生,我们用上「智能指针],让它帮我们管理吧:

classJudgeRole{public:std::stringJudge(conststd::stringroleName){ProductFactory

factory=ProductFactory

::Instance();std::shared_ptr

pRoleOperation(factory.GetProduct(roleName));returnpRoleOperation-Op();}};

采用了std::shared_ptr引用计数智能指针,我们不在需要时刻记住要手动释放资源的事情啦(我们通常都会忘记……),该智能指针会在当引用次数为0时,自动会释放掉指针资源。

来,我们接着来,除了工厂模式,策略模式也不妨试一试。

策略模式——它不香吗?

策略模式和工厂模式写起来其实区别也不大!策略模式也采用了面向对象的继承和多态机制。

在上面工厂模式代码的基础上,按照策略模式的指导思想,我们也来创建一个所谓的策略上下文类,这里命名为RoleContext:

很明显上面传入的参数operation就是表示不同的「策略」。我们在业务代码里传入不同的角色,即可得到不同的操作结果:

classJudgeRole{public:std::stringJudge(RoleOperation*pOperation){RoleContextroleContext(pOperation);returnroleContext.execute();}};intmain(){JudgeRolejudgeRole;std::coutjudgeRole.Judge(newRootAdminRole(ROLE_ROOT_ADMIN))std::endl;std::coutjudgeRole.Judge(newOrderAdminRole(ROLE_ORDER_ADMIN))std::endl;std::coutjudgeRole.Judge(newNormalRole(ROLE_NORMAL))std::endl;return0;}

当然,上面策略类还可以进一步优化:

用模板技术进一步封装,使其不限制于角色类。

使用方式,没太大差别,只需要指定类模板参数是基类(如本例RoleOperation)即可:

共勉

C++和Java语言都是面向对象编程的方式,所以都是可以通过面向对象和多态特性降低代码的耦合性,同时也可使得代码易扩展。所以对于写代码事情,不要着急下手,先思考是否有更简单、更好的方式去实现。

C++之父BjarneStroustrup曾经提及过程序员的三大美德是懒惰、急躁、傲慢,其中之一的懒惰这个品质,就是告知我们要花大力气去思考,避免消耗过多的精力个体力(如敲代码)。

原文链接:


转载请注明:http://www.aierlanlan.com/rzgz/3408.html