替代工厂模式的方法

前言

开放封闭原则,要求程序的扩展是开放的,程序的修改是封闭的。做到开放封闭的原则通常是针对抽象编程而不是具体编程,因为抽象是相对稳定的,扩展的开放则通过继承抽象体,覆写其方法,实现新方法的扩展。在c++中,无论是通过工厂模式,还是其他,似乎免不了switch case的处理,因为总会有创建具体子类的地方,代码大概会是长这个样子的:

ParentClass * ClassFactory(int classType)
{
    switch(classType)
    {
    case 1:
        ParentClass * p = new SubClass1();
        break;
    case 2:
        ParentClass * p = new SubClass2();
        break;
    ...
    }

    return p;
}

当代码里出现类似于这样的switch case时,会让人比较难于忍受,特别是当需求不断增多,扩展很多的时候,可能会出现几十处case语句。苛刻的讲,这是违背开放封闭原则的,一种新需求的产生,理论上只扩展一个子类即可,而不需要另加一条case语句,修改意味着架构的不稳定。

实现

一种可行的方法是借助于so部署。具体做法如下:
我们可能有一个模块,这个模块会针对客户的需求扩展,这时很容易想到通过抽象类的方式对这个模块的业务做抽象,一个抽象类这样产生了:

//该内容在TestInterface.h中
class TestInterface
{
public:
    TestInterface(){};
    virtual ~TestInterface(){};
    virtual void InterfacePrint() = 0;
};

typedef TestInterface* (*CallFunc)(void);

在这里简化流程,InterfacePrint作为这个模块抽象之后对外呈现的行为,TestA是一个实现了InterfacePrint的子类,
TestA.h内容如下:

#include "TestInterface.h"

class TestA:public TestInterface
{
public:
    TestA();
    ~TestA();
    void InterfacePrint();
};

extern "C"
{
TestInterface * getInstance();
}

TestA.cpp内容如下:

#include "TestA.h"
#include <iostream>
using namespace std;

TestA::TestA()
{
    cout<<"TestA Construct"<<endl;
}

TestA::~TestA()
{
    cout<<"deconstruct TestA"<<endl;
}

void TestA::InterfacePrint()
{
    cout<<"TestA Interface Print"<<endl;
}

TestInterface * getInstance()
{
    TestInterface * p = new TestA();
    return p;
}

把TestA.cpp编译成一个独立的so文件:

g++ -c  TestA.cpp
g++ -fPIC -shared TestA.o -o libTestA.so

新的需求来了,我们用TestB来做扩展:
TestB.h内容如下:

#include "TestInterface.h"

class TestB:public TestInterface
{
public:
    TestB();
    ~TestB();
    void InterfacePrint();
};

extern "C"
{
TestInterface * getInstance();
}

TestB.cpp内容如下:

#include "TestB.h"
#include <iostream>
using namespace std;

TestB::TestB()
{
    cout<<"TestB Construct"<<endl;
}

TestB::~TestB()
{
    cout<<"deconstruct TestB"<<endl;
}

void TestB::InterfacePrint()
{
    cout<<"TestB Interface Print"<<endl;
}

TestInterface * getInstance()
{
    TestInterface * p = new TestB();
    return p;
}

生成libTestB.so,

g++ -c  TestB.cpp
g++ -fPIC -shared TestB.o -o libTestB.so

在这里用Procedure.cpp作为middle ware处理一些逻辑,
Procedure.h内容如下:

#include "TestInterface.h"

TestInterface * OpenInterface(char * pszModuleName);

Procedure.cpp内容如下:

#include "TestInterface.h"
#include <dlfcn.h>
#include <string.h>
#include <iostream>
using namespace std;

TestInterface * OpenInterface(char * pszModuleName)
{
    char szLibName[64] = {0};
    sprintf(szLibName,"./lib%s.so",pszModuleName);
    cout<<szLibName<<endl;
   
    void * handle = NULL;
    char * error ;
    handle = dlopen(szLibName,RTLD_LAZY);
    if(!handle)
    {
        cout<<"dlopen failed"<<endl;
        return NULL;
    }
    error = dlerror();
    if(error)
    {
        cout<<error<<endl;
    }else
    {
        cout<<"success"<<endl;
    }

    CallFunc callFunc = NULL;
    *(void**)(&callFunc) = dlsym(handle,"getInstance");

    error = dlerror();
    if(error)
    {
        cout<<error<<endl;
    }
    else
    {
        cout<<"dlsym success"<<endl;
    }
    
    if(!callFunc)
    {
        cout<<"get symbol failed"<<endl;
        dlclose(handle);
        return NULL;
    }
    TestInterface * pInterface = NULL;
    pInterface = (*callFunc)();
    if(!pInterface)
    {
        cout<<"get Install failed"<<endl;
        dlclose(handle);
        return NULL;
    }
   
    //dlclose(handle);  
    return pInterface;
}

OpenInterface()函数的逻辑还算简单,参数用于组成具体的so文件名,通过dlopen,dlsym拿到so库文件里的getInstance函数symbol,然后通过symbol获取到具体子类的对象。前面TestA.cpp\TestB.cpp中extern "C"的作用在于生成在so里的getInstance symbol是C语言的symbol命名规则,而不是c++ mangle过的symbol。

最后看下客户层次的代码,main.cpp

#include <iostream>
#include "Procedure.h"

using namespace std;

int main(int argc, char** argv)
{
    if(argc < 2)
    {
        cout<<"argv error"<<endl;
        return -1;
    }
    cout<<"argc is "<<argc<<"argv[0]"<<argv[0]<<"argv[1]"<<argv[1]<<endl;
    TestInterface * p = NULL;

    p = OpenInterface(argv[1]);

    if(!p)
    {
        return -1;
    }
    cout<<"begin InterfacePrint"<<endl;
    p->InterfacePrint();
    delete p;
    return 0;
}

main函数接受参数,参数即为子类的模块名字(TestA,TestB...)

回顾

通过这种方式,扩展就只是扩展,继承抽象类,实现接口,封装在so中,main.cpp、Procedure.cpp是稳定的,对外暴露的是抽象的接口,即TestInterface抽象类,还有lib的名字,从而消除了switch case的存在。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容