当前位置:首页 > 单片机 > C语言与CPP编程
[导读]每日一句英语学习,每天进步一点点: "Without purpose, the days would have ended, as such days always end, in disintegration." 「少了目标,一天还是会结束,它总是以支离破碎的形式结束。」 前言 羊哥之前写一篇有趣的文章《答应我,别再if/else走天下

<section>
每日一句英语学习,每天进步一点点:
  • "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可以说是我们学习编程时,第一个学习的分支语句,简单易理解,生活中也处处有的if/else例子:

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

老婆的思维:

买一斤包子;
if( 看到卖西瓜的 )
  买一只( 西瓜 );

而程序员老公的程序:

if( ! 看见卖西瓜的 ) 
   买一斤包子;
else
   买一只( 包子 );

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

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

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

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

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

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

class JudgeRole
{

public:
    std::string Judgestd::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/caseif/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 曾经提及过程序员的三大美德是懒惰、急躁、傲慢,其中之一的懒惰这个品质,就是告知我们要花大力气去思考,避免消耗过多的精力个体力(如敲代码)。


section>

点【在看】是最大的支持 

免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭