替代工厂模式的方法

前言

开放封闭原则,要求程序的扩展是开放的,程序的修改是封闭的。做到开放封闭的原则通常是针对抽象编程而不是具体编程,因为抽象是相对稳定的,扩展的开放则通过继承抽象体,覆写其方法,实现新方法的扩展。在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的存在。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335

推荐阅读更多精彩内容