欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

流程控制引擎组件化

程序员文章站 2022-04-11 21:29:13
在较大规模的业务系统中经常会有这样的模块,它按照一定的业务流程调用其它模块来实现一定的业务逻辑,我们姑且称之为流程引擎。这里称之为引擎有两层含义,一、突显其在业务系统的核心重要位置。二、它又是复杂不好维护的,通常由资深程序员把持。这样的引擎不仅代码繁多,与各个模块的接口复杂,并且一定程度对外是不透明... ......

                 流程控制引擎组件化

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

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

流程控制引擎组件化

 

 

数控机床

流程控制引擎组件化

机器人流水线

   

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

流程控制引擎组件化

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

流程控制引擎组件化

 

 

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

   

   流程控制引擎组件化

 

 流程控制引擎组件化

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

流程控制引擎组件化

 

  

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

组件节点:

流程控制引擎组件化

流程控制引擎组件化

 

 例子:

 流程控制引擎组件化

流程控制引擎组件化

  这里的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.\n", LOG_PREFIX, iComponentID,
              pComponent);

    if (iComponentID != NONE_COMPONENT && pComponent == NULL)
    {
        ADD_TRACE("%s RatingCtrlFactory::CreateComponent OUT: iComponentID: %d, pComponent: %p.\n", 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.\n", 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.\n", LOG_PREFIX);
            return m_pComponentA->Execute(pRatableEvent);
        }
    case EXECUTE_ID_UPDATE:
        {
            ADD_TRACE("%s ExecuteProcessIN::Execute() End Goto EXECUTE_ID_UPDATE Process.\n", LOG_PREFIX);
            return m_pComponentB->Execute(pRatableEvent);
        }
    case EXECUTE_ID_FINISH:
        {
            ADD_TRACE("%s ExecuteProcessIN::Execute() End Goto EXECUTE_ID_FINISH Process.\n", LOG_PREFIX);
            return m_pComponentC->Execute(pRatableEvent);
        }
    case EXECUTE_ID_EMCDR:
        {
            ADD_TRACE("%s ExecuteProcessIN::Execute() End Goto EXECUTE_ID_EMCDR Process.\n", 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]\n",
                      LOG_PREFIX, iExecuteId, pRatableEvent.GetAttrEx(EA::OCS_RESULT_CODE)->AsInteger());
        }
    }

    ADD_TRACE("%s ExecuteProcessIN::Execute() Fail.\n", LOG_PREFIX);
    return false;
}

最终复杂的流程图:

流程控制引擎组件化