只需要在前期规划的过程中划分好最基本的模块就可以方便后期的设计,当然前期的划分方式也直接决定后期开发的难度和速度。因此前期的分配过程是体现设计师能力的重点。
在Linux中驱动程序的添加和移除就是典型的模块化操作,这样也就方便了基本的操作。这样通过模块的注册和注销操作就能够实现模块的添加。而且在Linux中模块的注册和释放都是采用同样的函数实现。因此我们可以想象在我们的模块化设计中也可以采用类似的方式实现。也就是通过统一的注册方式。但是具体的模块操作采用回调函数的形式,这与linux驱动中的file_operations结构体具有异曲同工之妙,也就是通过一个结构体实现不同的函数接口,而基本的实现过程需要我们在设计驱动的过程中进行实现,也就是采用函数指针的方式进行调用,即实现回调。这样我们就能实现不同模块的具体操作。
模块的管理应该包含两个过程:注册和注销,同时注册又包括初始化和启动,注销包括停止和撤销操作。因此可以采用一个4种状态的状态图表示。
但是很多情况下的模块化存在一定的依赖关系,这与我们在写Makefile中的依赖一样,需要创建一些其他的模块才能创建当前模块。为了解决这种问题,我们通常对模块进行分层设计。也就是将存在一定依赖关系的模块放在不同的层次上,通过逐层逐层的初始化方式进行模块初始化。这样就能较好的解决依赖问题。但是有时候同层也会存在一定的依赖关系,这时我们可以采用分级的方式解决一定的依赖关系。因此我们可以将依赖关系最高的模块放在最低的层次上,先进行初始化,这样方便其他的模块对其进行依赖。然后在对其他的层次初始化。
在实际的开发中通常采用不同三层模式,即:平台层(这层的模块对象先初始化),框架层(第二初始化),应用层(最后初始化)。同层的依赖关系也可以采用分级(本质与分层是相同的)解决。
但是在撤销的过程中我们需要注意的是,撤销的顺序必须与初始化的顺序相反,这也是依赖关系所导致的,只有先撤销后初始化的模块才不会破坏对其他模块的影响。
模块的管理是模块化设计中非常重要的一环节,采用回调函数的方式能够很好的解决不同模块的初始化操作。因此通用的函数接口也是不断总结和分析得到的。
链表在模块化管理中有很重要的作用,记得在Linux中链表中不存在内容,只是两个指针,但是通常将链表嵌入到模块结构体中就能实现不同模块的级联。
分层的方式下可以采用链表将该层中所有的模块对象关联起来,也就是将一个简单的链表结构体节点嵌入到模块结构体中,通常放在第一个元素的位置上,这样就可以通过链表指向的地址表示模块的地址(第一个元素的地址和结构体的地址是相同的这一原理)。当然也可以不放在第一个元素的位置上,这在 linux源码中经常可发现。
typedef struct list_node
{
struct list *prev;
struct list *next;
} list_node;
typedef struct list
{
struct list* prev;
struct list* next;
int list_count;
} list;
typedef struct module_object
{
list_node _node;
....
} module_object;
模块和层次的实现可以采用枚举型结构事先安排好所有的模块和所需要的层次,但是最好在其中设置相应的统计参数,便于参数的检查。
比如:
typedef enum
{
/*不同的模块*/
module0,
module1,
module2,
...
moduleN-1,
/*统计模块的总数*/
module_count,
/*记录最后的一个标号*/
module_last = module_count -1,
}modules;
typedef enum
{
//platform layer,平台层分级
PLayer0,
PLayer1,
...
PLayerM-1,
//framework layer,框架层分级
FLayer0,
FLayer1,
...
FLayerN-1,
//application layer,应用层分级
ALayer0,
ALayer1,
...
ALayerT-1,
//统计层数
layer_count,
layer_last = layer_count - 1,
}Layer;
上面就能实现分层和模块的管理,通过下标就能访问具体的层和具体的模块。当然这些都需要我们在实际的设计中首先规划好所有的层次和模块关系。
其实反过来看看linux的模块化管理真的是做的非常好,因此多去理解linux源码对我们的软件设计有很大的提高,重要的是思想。