zoukankan      html  css  js  c++  java
  • 流程控制引擎组件化

                     流程控制引擎组件化

      在较大规模的业务系统中经常会有这样的模块,它按照一定的业务流程调用其它模块来实现一定的业务逻辑,我们姑且称之为流程引擎。这里称之为引擎有两层含义,一、突显其在业务系统的核心重要位置。二、它又是复杂不好维护的,通常由资深程序员把持。这样的引擎不仅代码繁多,与各个模块的接口复杂,并且一定程度对外是不透明的,就像一个黑盒模块。当一个新手想对其进行哪怕是一点点修改的时候都将会是一个灾难。本文就来讨论如何将这样一个庞大的引擎进行组件化改造,使其拥有代码级的流程图,使代码的维护难度降低一个量级,让新手也可以很快知道要如何修改代码,给引擎增加新的功能。

      我们来看两张图,分别是数控机床和机器人流水线。他们都是自动化的,就像我们的业务流程引擎一样。数控机床简洁、高效。但维护的时候需要专业人员,想增加新功能几乎不可能。机器人流水线,透明,看着有点复杂。但可以随时对流水线上任意部分进行升级,改造,替换。回到我们的业务流程引擎,我们到底希望是上面的哪一种?不难想到,由于业务需求的易变性,我们更倾向于第二种方案。但往往我们的代码是第一种情况,并且代码的组织是混乱的,并不是数控机床那种优雅。

    数控机床

    机器人流水线

       

      当我们面对一个不熟悉的引擎的时候,我们看到的是下面这样子。

      当我们花了一个月时间,仔细研读了几万行的代码后,我们看到的是下面这样子。

      虽然我们已经了解了引擎内部的情况,但就像图中所描绘的样子,当要给引擎增加一个新的功能的时候,新增的功能和代码很容易影响原来正确的系统,一个小局部(零件)的问题,都会影响到整个系统。那么,我们如何来改变现有的流程引擎,以实现方便地增加新的功能?

       

       

     

      如上图所示,如果我们的流程是一个流水线的话,我们就可以轻易地在流水线中增加新的组件,以实现新的功能。流程组件具有这样的特征:每个组件都是一个独立的个体,不受其它组件的影响。组件具有统一的接口,新增的组件可以方便和原有的组件一起工作。可以根据业务需要挑选合适的组件,组成一个新的业务流程。当把这些组件组合到一起,就形成了组件链表的概念。一个个独立的组件,就像链表里面的一个个节点,组件节点通过组件指针连接起来。一个流程的执行过程就像是对这个链表的遍历。并且这个链表不仅可以只是个单链表,而且可以是多分支链表,以应对流程中多条件分支的情况。

      

      下面我们来看看代码是如何来实现的。

    组件节点:

     例子:

     

      这里的Execute()函数就是实现每个业务组件具体功能的地方,在这个函数里面可能调用其它模块。

    空组件节点:

     

     

    业务组件节点:

     

       开闭原则:每个组件都是一个独立体(类),组件之间是一种松耦合的关系。流程中某个组件的改动,或者新增加一个组件,并不会影响到其它组件。支持以一种“对扩展开放,对修改封闭”的方式来为流程增加新的功能。从而不会将错误引入原有的稳定、正确的代码中。组件就像流水线上的每个节点,独立可替换。

      流程组装:每个组件都有一个统一的编号ID。初始化时,通过按一定顺序读入这些ID,来进行流程的组装。流程中每个功能模块的调用顺序不再是固定写死的,而是在初始化时动态配置生成的。从而使流程功能的调整(增加,删除)变得很容易,不再用修改代码,而是调整流程组装配置列表的参数。

      由于流程可能是复杂的,具有复杂的分支情况,这里我们设计了一套编码方法来应对这个情况。

      流程编码方法:

      

     

      当我们有如下流程图需要实现的时候,下面图片可另外窗口打开。

      就可以按上面的编码方案生成相应的流程代码,注意这里面说的是代码。这里的流程组装配置列表与流程图是一一对应的。从流程组装配置列表就可以轻松看到计费控制的整个流程图,而不用再发很多时间去了解代码,进而再画流程图。代码化的流程图是自维护的,即跟实际的情况是同步的,不再需要另外地去维护。想像一下,当你接到一个需要维护的复杂引擎的时候,这个引擎的流程图就在你面前,而不再需要你花费一个月时间,阅读数万行代码,自己一点点去画出来。这是何等的进步——The source code is the design

    流程组装配置列表

      组件化的优点:

      a)将流程和被流程调用的子模块进行解耦。例如:批价函数,m_pPriceGuiding->ExecuteRating();在现有的在线流程中共有38处地方进行了调用,而按新的设计方案只会在一个组件(ExecuteRating)中进行调用,要用的批价的地方,只要将其加到流程组装配置列表即可。可以想象,如果ExecuteRating()函数接口改变了,对于原有的在线流程将会是一个灾难,而新的设计将可以轻松应对。注意:这并不是一个特例,在流程中几乎所有对子模块的调用都存在这种情况。

      b)组件的复用和移植。新的设计改变了原来以整个流程为最小单位的方式,而是一个组件为系统的最小单位。每个组件都是功能独立的,互不影响的方法集。不仅可以在不同流程间进行复用,甚至于可以跨版本地复用。包括组件移植时,成本也是很低的,只要将组件的代码文件进行移植即可,移植的是文件而不是代码。

      c)一些组件会趋于稳定。原来以流程为最小单位设计,任何的改变都会影响到整个流程,导致每次升版时流程的代码总是有很大的变化。而新的设计以组件为系统最小的单位,有一些组件会趋于稳定,一定程度上保持不变。从而分隔了系统中变化和不变的部分。使系统从整体上更加健壮。

      下面介绍一些代码的细节。

    RatingCtrlComponent.h
    #ifndef RATINGCTRLCOMPONENT_H_
    #define RATINGCTRLCOMPONENT_H_
    
    class RatingCtrlComponent
    {
    public:
        virtual ~RatingCtrlComponent()
        {
        }
        /**
        * @brief 组件流程执行接口
        *
        * @param pRatableEvent [in/out]
        * @return bool
        */
        virtual bool Execute(TRatableEvent& pRatableEvent) = 0;
    };
    
    #endif
    RatingCtrlDectorator .h
    #ifndef RATINGCTRLDECTORATOR_H_
    #define RATINGCTRLDECTORATOR_H_
    
    #include "RatingCtrlComponent.h"
    
    class RatingCtrlDectorator : public RatingCtrlComponent
    {
    public:
        RatingCtrlDectorator(RatingCtrlComponent* pComponentA) : m_pComponentA(pComponentA)
        {
        }
        virtual ~RatingCtrlDectorator()
        {
        }
        /**
        * @brief 组件流程执行接口
        *
        * @param pRatableEvent [in/out]
        * @return bool
        */
        virtual bool Execute(TRatableEvent& pRatableEvent) = 0;
    
    protected:
        RatingCtrlComponent* m_pComponentA; ///< 该组件的下一个组件(A分支)
    };
    
    #endif
    ComponentFactory.h
    #ifndef COMPONENTFACTORY_H_
    #define COMPONENTFACTORY_H_
    
    
    #include <list>
    #include <vector>
    #include "ComponentDefine.h"
    #include "RatingCtrlComponent.h"
    
    
    class ComponentFactory
    {
    public:
        ComponentFactory()
        {
        }
        virtual ~ComponentFactory()
        {
            ClearComponent();
        }
    
        /**
         * @brief 装配组件
         *
         * 根据编码[一个数组]装配组件。
         * 将各个组件构成一个流程分支链表。
         *
         * @param viComponentID [in]
         * @return RatingCtrlComponent 流程的头结点
         */
        RatingCtrlComponent* BuildComponent(vector<int>& viComponentID);
        void ClearComponent();
    
    protected:
        virtual RatingCtrlComponent* CreateComponent(int iComponentID, RatingCtrlComponent* pComponent,
                                                     list<RatingCtrlComponent*>& lpComponent) = 0;
    
    private:
        list<RatingCtrlComponent*> m_lpComponent;
        vector<RatingCtrlComponent*> m_vpComponent;
    };
    
    #endif
    ComponentFactory.cpp
    #include "ComponentFactory.h"
    
    void ComponentFactory::ClearComponent()
    {
        vector<RatingCtrlComponent*>::iterator itr;
    
        for (itr = m_vpComponent.begin(); itr != m_vpComponent.end(); ++itr)
        {
            delete * itr;
            *itr = NULL;
        }
    }
    
    
    /*
    下面的代码很短,却完了一件精妙的工作.组件以一种近似网状的结构组织起来,
    就像所有的网络流算法一样,程序的执行过程就是选择一条路径遍历的过程.
    不同的是这边的分支是根据业务不同的而进行选择的。如此复杂的想法却以
    这么简单的方式来实现,甚为精妙!-----by liang.shishi
    */
    RatingCtrlComponent* ComponentFactory::BuildComponent(vector<int>& viComponentID)
    {
        if (viComponentID.empty())
            return NULL;
    
        m_lpComponent.clear();
    
        RatingCtrlComponent* pComponent = NULL;
    
        vector<int>::iterator itr = viComponentID.end() - 1;
        // 增加默认基础类
        if (*itr != NONE_COMPONENT)
        {
            viComponentID.push_back(NONE_COMPONENT);
        }
    
        for (itr = viComponentID.end() - 1; itr >= viComponentID.begin(); --itr)
        {
            if (*itr < 0)
            {
                if (*itr == -1)
                {
                    // 正常分支结束后,放在容器后面
                    m_lpComponent.push_back(pComponent);
                }
                else
                {
                    for (int i = 0; i < (*itr)*(-1); i++)
                    {
                        // 多分支情况,放容器前面
                        m_lpComponent.push_front(pComponent);
                    }
                }
    
                // 处理以0结束的分支
                if ((itr - 1) >= viComponentID.begin() && *(itr - 1) == 0)
                {
                    pComponent = NULL;
                }
                else
                {
                    pComponent = m_lpComponent.front();
                    m_lpComponent.pop_front();
                }
    
                continue;
            }
    
            pComponent = CreateComponent(*itr, pComponent, m_lpComponent);
    
            // 组装失败
            if (pComponent == NULL)
            {
                ClearComponent();
                // LOG
                return NULL;
            }
    
            m_vpComponent.push_back(pComponent);
        }
    
        return pComponent;
    }
    RatingCtrlFactory .h
    #ifndef RATINGCTRLFACTORY_H_
    #define RATINGCTRLFACTORY_H_
    
    #include "ComponentFactory.h"
    
    
    class RatingCtrlFactory : public ComponentFactory
    {
    public:
        /**
         * @brief 创建组件
         *
         * 根据iComponentID创建对应的组件,用pComponent初始化新组件,最后返回新组件给pComponent。
         * 对于分支组件,需要根据lpComponent做特殊处理~
         *
         * @param iComponentID [in]
         * @param pComponent [in/out]
         * @param lpComponent [in/out]
         */
        virtual RatingCtrlComponent* CreateComponent(int iComponentID, RatingCtrlComponent* pComponent,
                                                     list<RatingCtrlComponent*>& lpComponent);
    
    private:
        RatingCtrlComponent* Back(list<RatingCtrlComponent*>& lpComponent);
    };
    
    #endif
    #include "RatingCtrlFactory.h"
    #include "NoneComponent.h"
    
    RatingCtrlComponent* RatingCtrlFactory::Back(list<RatingCtrlComponent*>& lpComponent)
    {
        RatingCtrlComponent* pComponent = lpComponent.back();
        lpComponent.pop_back();
    
        return pComponent;
    }
    
    // 当遇到多分支的时候,应该先将pComponent放回链表首,再从链表尾取出相应数量的组件指针进行组装.
    RatingCtrlComponent* RatingCtrlFactory::CreateComponent(int iComponentID, RatingCtrlComponent* pComponent,
                                                            list<RatingCtrlComponent*>& lpComponent)
    {
        ADD_TRACE("%s RatingCtrlFactory::CreateComponent IN: iComponentID: %d, pComponent: %p.
    ", LOG_PREFIX, iComponentID,
                  pComponent);
    
        if (iComponentID != NONE_COMPONENT && pComponent == NULL)
        {
            ADD_TRACE("%s RatingCtrlFactory::CreateComponent OUT: iComponentID: %d, pComponent: %p.
    ", LOG_PREFIX, iComponentID,
                      pComponent);
            return NULL;
        }
    
    
        switch (iComponentID)
        {
        case NONE_COMPONENT:
            {
                if (pComponent != NULL)
                {
                    return NULL;
                }
    
                pComponent = new NoneComponent();
                break;
            }
        case EXECUTE_INITIALIZE:
            {
                pComponent = new ExecuteInitialize(pComponent);
                break;
            }
        case EXECUTE_PROCESS_IN:
            {
                lpComponent.push_front(pComponent);
    
                if (lpComponent.size() < 4)
                {
                    return NULL;
                }
    
                pComponent = new ExecuteProcessIN(Back(lpComponent), Back(lpComponent), Back(lpComponent), Back(lpComponent));
    
                break;
            }
            default:
            {
                return NULL;
            }
        }
    
        return pComponent;
    }

    复杂分机的代码实现:

    #ifndef EXECUTEPROCESSIN_H_
    #define EXECUTEPROCESSIN_H_
    
    #include "RatingCtrlDectorator.h"
    
    class ExecuteProcessIN : public RatingCtrlDectorator
    {
    public:
        ExecuteProcessIN(RatingCtrlComponent* pComponentA, RatingCtrlComponent* pComponentB, RatingCtrlComponent* pComponentC,
                         RatingCtrlComponent* pComponentD) : RatingCtrlDectorator(pComponentA), m_pComponentB(pComponentB),
                                                             m_pComponentC(pComponentC), m_pComponentD(pComponentD)
        {
        }
        virtual ~ExecuteProcessIN()
        {
        }
    
        /**
         * @brief IN业务流程选择
         *
         * 根据 ExecuteId 选择流程
         * A:初始包流程; B:更新包流程; C:结束包鉴权; D:异常流程
         *
         * @param pRatableEvent [in]
         * @return bool
         */
        virtual bool Execute(TRatableEvent& pRatableEvent);
    
    private:
        RatingCtrlComponent* m_pComponentB;
        RatingCtrlComponent* m_pComponentC;
        RatingCtrlComponent* m_pComponentD;
    };
    
    #endif
    #include "ExecuteProcessIN.h"
    
    bool ExecuteProcessIN::Execute(TRatableEvent& pRatableEvent)
    {
        ADD_TRACE("%s ExecuteProcessIN::Execute() Begin.
    ", LOG_PREFIX);
    
        int iExecuteId = pRatableEvent.GetAttrEx(EA::EXECUTE_ID)->AsInteger();
    
        switch (iExecuteId)
        {
        case EXECUTE_ID_INIT:
            {
                ADD_TRACE("%s ExecuteProcessIN::Execute() End Goto EXECUTE_ID_INIT Process.
    ", LOG_PREFIX);
                return m_pComponentA->Execute(pRatableEvent);
            }
        case EXECUTE_ID_UPDATE:
            {
                ADD_TRACE("%s ExecuteProcessIN::Execute() End Goto EXECUTE_ID_UPDATE Process.
    ", LOG_PREFIX);
                return m_pComponentB->Execute(pRatableEvent);
            }
        case EXECUTE_ID_FINISH:
            {
                ADD_TRACE("%s ExecuteProcessIN::Execute() End Goto EXECUTE_ID_FINISH Process.
    ", LOG_PREFIX);
                return m_pComponentC->Execute(pRatableEvent);
            }
        case EXECUTE_ID_EMCDR:
            {
                ADD_TRACE("%s ExecuteProcessIN::Execute() End Goto EXECUTE_ID_EMCDR Process.
    ", LOG_PREFIX);
                return m_pComponentD->Execute(pRatableEvent);
            }
        default:
            {
                pRatableEvent.SetAttr(EA::OCS_RESULT_CODE, CCA::DIAMETER_RATING_FAILED);
                ADD_ERROR("ZSmart-Charging-Online-1015",
                          "%s ExecuteProcessIN::ExecuteRatableEvent() GetExecuteId Failed. iExecuteId = [%d], ReturnCode = [%d]
    ",
                          LOG_PREFIX, iExecuteId, pRatableEvent.GetAttrEx(EA::OCS_RESULT_CODE)->AsInteger());
            }
        }
    
        ADD_TRACE("%s ExecuteProcessIN::Execute() Fail.
    ", LOG_PREFIX);
        return false;
    }

    最终复杂的流程图:

  • 相关阅读:
    通过actionlib控制jaco机械臂
    actionlib学习
    配置 jaco机械臂 ros环境
    ubuntu14.04标题栏显示上下网速
    linux下alsa架构音频驱动播放wav格式文件
    ros语音交互(五)移植科大讯飞语音识别到ros
    ubuntu14.04 wifi驱动
    Ubuntu14.04使用apt-fast来加快apt-get下载的教程
    ROS语音交互(四)接入图灵语义理解
    相比传统游戏,区块链游戏的价值在哪里?
  • 原文地址:https://www.cnblogs.com/cdap/p/9454835.html
Copyright © 2011-2022 走看看