郑重说明,正向(或者前向)插件系统和反向(或者后向)插件系统均为我个人杜撰出来的词汇,他们是一对概念上相对的设计模式。
————————————————————————————————————
正向插件系统指的是在设计之初就已经定义好了一族用于特定功能的接口,然后通过共享库的形式隔离不同的实现策略,这样做不仅可以做到运行时按需选择实现策略,而且非常有利于功能的模块化测试。这种模式的插件系统是非常常见的,比如Adobe PS 对于不同文件格式、不同滤镜等的支持就是采用的就是正向插件系统设计。
为了说明这种思想的设计过程,我将使用一个简单的例子来说明,并在完成这个例子的过程中逐步展示这种设计模式的核心。假设现在我们需要实现一个计算器程序,是的,就是一个计算器程序,大家先不要着急扔鸡蛋嘛!虽然计算器程序的例子被太多的场合使用,但是为了简单起见,它在这里还是比较合适的。当然这个计算器的功能很简单,只有+、-、*、/这四种运算,现在我们来看一下这样的计算器程序怎么使用正向插件系统的思想进行实现。
首先,我们在源代码中实现一个简单的加法运算:从控制台输入两个数字,然后输出这两个数字的和。
main.c文件的实现看起来如下:
#include <stdio.h>
#include <stdlib.h>
int add(int a, int b) {
return a + b;
}
int main() {
do {
int i_first;
printf("input the first number:");
scanf("%d", &i_first);
int i_second;
printf("input the second number:");
scanf("%d", &i_second);
int i_result = add(i_first, i_second);
printf("the result is %d\n\n", i_result);
} while (true);
return 0;
}
然后因为我们知道+、-、*、/都是二则运算,这样Add函数就可以普通化成int Clac(int, int)函数,然后我们要将这个Calc函数放到一个单独的共享库中实现。
主项目main.c的代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <assert.h>
typedef int (*calc_t)(int a, int b);
int main() {
void* module = dlopen("./libadd.dylib", RTLD_NOW);
assert(module != NULL);
calc_t calc = (calc_t) dlsym(module, "calc");
assert(calc != NULL);
do {
int i_first;
printf("input the first number:");
scanf("%d", &i_first);
int i_second;
printf("input the second number:");
scanf("%d", &i_second);
int i_result = (*calc)(i_first, i_second);
printf("the result is %d\n\n", i_result);
} while (true);
dlclose(module);
return 0;
}
libadd项目的add.cpp文件内容如下:
extern "C" int calc(int a, int b) {
return a + b;
}
我们看到程序能够正确的运行,但是现在我们只实现了一个+的插件,我们继续实现-、*、/的插件,其内容分别为:
libminus项目的minus.cpp文件内容如下:
extern "C" int calc(int a, int b) {
return a - b;
}
libmultiply项目的multiply.cpp文件内容如下:
extern "C" int calc(int a, int b) {
return a * b;
}
libdivide项目的divide.cpp文件内容如下:
extern "C" int calc(int a, int b) {
return a / b;
}
修改主项目的main.cpp文件中加载的动态库,可单独测试每一个插件是否正确运行。测试结果是通过。
接下来,我们必须在主项目中管理这些组件,然后根据用户输入的信息动态的选择应该使用的插件。一个简单的版本如下:
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <assert.h>
typedef int (*calc_t)(int a, int b);
struct dl_mod {
void* module;
calc_t calc;
} mod[4];
int main(int argc, char* argv[]) {
for (int i = 0; i < 4; i++) {
void* module = dlopen(argv[i + 1], RTLD_NOW);
assert(module != NULL);
calc_t calc = (calc_t) dlsym(module, "calc");
assert(calc != NULL);
mod[i].module = module;
mod[i].calc = calc;
}
do {
int i_calc;
printf("select the calculate number:");
scanf("%d", &i_calc);
int i_first;
printf("input the first number:");
scanf("%d", &i_first);
int i_second;
printf("input the second number:");
scanf("%d", &i_second);
int i_result = (*(mod[i_calc].calc))(i_first, i_second);
printf("the result is %d\n\n", i_result);
} while (true);
for (int i = 0; i < 4; i++) {
dlclose(mod[i].module);
}
return 0;
}
当然这个版本有一个比较要命的问题,你必须知道在命令行中输入的组件的顺序,然后才能准确调用那个组件。为了解决这个问题,我们可以给插件增加一些元数据。修改后的版本如下:
libadd项目增加如下代码:
extern "C" char* description() {
return "add";
}
libminus项目增加如下代码:
extern "C" char* description() {
return "minus";
}
libmultiply项目增加如下代码:
extern "C" char* description() {
return "multiply";
}
libdivide项目增加如下代码:
extern "C" char* description() {
return "divide";
}
主项目的代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <assert.h>
typedef char* (*description_t)();
typedef int (*calc_t)(int a, int b);
struct dl_mod {
void* module;
char* desc;
calc_t calc;
} mod[4];
int main(int argc, char* argv[]) {
for (int i = 0; i < 4; i++) {
void* module = dlopen(argv[i + 1], RTLD_NOW);
assert(module != NULL);
description_t desc = (description_t) dlsym(module, "description");
assert(desc != NULL);
calc_t calc = (calc_t) dlsym(module, "calc");
assert(calc != NULL);
mod[i].module = module;
mod[i].desc = (*desc)();
mod[i].calc = calc;
}
do {
for (int i = 0; i < 4; i++) {
printf("%d:%s\n", i, mod[i].desc);
}
int i_calc;
printf("select the calculate number:");
scanf("%d", &i_calc);
int i_first;
printf("input the first number:");
scanf("%d", &i_first);
int i_second;
printf("input the second number:");
scanf("%d", &i_second);
int i_result = (*(mod[i_calc].calc))(i_first, i_second);
printf("the result is %d\n\n", i_result);
} while (true);
for (int i = 0; i < 4; i++) {
dlclose(mod[i].module);
}
return 0;
}
运行结果如下:
./calc libadd.dylib libminus.dylib libmultiply.dylib libdivide.dylib
0:add
1:minus
2:multiply
3:divide
select the calculate number:0
input the first number:3
input the second number:4
the result is 7
0:add
1:minus
2:multiply
3:divide
select the calculate number:
好了,前向插件系统的设计基本已经都展示完了。当前这是一个简单的实例,简单的接口,简单的管理。
如果读者有兴趣了解更多关于这方面的知识,我推荐可以简单研究一下notepadd++的插件系统。