经典软件设计方法 - 插件方法
介绍
什么是插件方法,就是将某些模块以插件的方式添加到现有系统中来完成某个具体功能,由于这个具体功能可以由很多不同的模块完成,而每次一般只需要其中一个模块,所以必须可以动态更换模块。这在linux驱动程序中使用很多,如摄像头驱动有一个总的模块接口v4l2,而根据不同的摄像头需要动态注册不同的驱动程序,这些驱动程序就类似插件。在处理这种问题时,很多人可能想到C++的类继承,设置抽象基类定义接口,然后具体子类来实现具体的不同实现,但是有时C++的类继承无法解决所有问题。如和其他人合作时,合作方只提供动态库,如果使用类继承,也就是我们在使用时必须知道合作方的类名字,否则无法使用,而且如果合作方修改了类名称,我们的代码也要修改,所以需要寻找其他更好的方法。那么这里介绍的插件方法就是解决类似问题很好的办法。
UML
虽然该问题不是类继承,但是可以用UML类图更清楚地说明这个问题,module_method_t定义了插件的接口,类似于抽象类;module1和module2是两个实现了该插件接口的独立模块,这两个模块可以编译成动态库;使用者client可以根据需求选择加载module1还是加载module2,使用者虽然调用的抽象类定义的接口,其实真正的实现都在module1或者module2中实现的。
代码示例
module.h
typedef struct _module_method_t{ void* attr1; void* attr2; void (*Func1)(); void (*Func2)(); }module_method_t;
module1.c
module_method_t module = { attr1 : data1 pointer, attr2 : data2 pointer, Func1 : module1_Func1, Func2 : module1_Func2, }; void module1_Func1(){ /*do something*/ } void module1_Func2(){ /*do something*/ }
module2.c
module_method_t module = { attr1 : data1 pointer, attr2 : data2 pointer, Func1 : module2_Func1, Func2 : module2_Func2, }; void module2_Func1(){ /*do something*/ } void module2_Func2(){ /*do something*/ }
client.c
void init_module(){ void* handle = dlopen("libmodule.so", RTLD_NOW); void* module = dlsym(handle, "module"); /*do something*/ }
其他
加载过程,除了使用通过dlsym查找动态库变量符号外,还有其他两种方法,一个是接口定义统一的全局创建(初始化)函数接口,module1和module2都去实现,其实这个和上面的老子一样,只是由全局变量变为了全局函数;第二个方法是client或者接口部分定义一个注册接口,module1和module2主动注册,但是调用注册接口也需要另外一个函数,而这个函数应该也是由接口定义的全局函数,所以和刚说的第一种方法也一样,所以不管使用哪种办法,原理都是一样的。