2010年3月31日

1.1 BUILDER生成器

1、 意图

将一个复杂对象的构建和它的表示分离,使得同样的构建过程可以创建不同的表示。

构建是通过对生成器抽象接口的调用实现的。而构建出来的产品的类型(表示),是由具体的进行构建的生成器的子类的实例来决定的。这样导向器只需要调用生成器的抽象接口生成产品,而具体生成什么样的产品,则是根据导向器配置的具体的生成器实例相关。

终于理解了它是如何把复杂对象的构建和它的表示分离的,也理解了为什么同样的构建过程。

2、 动机

3、 适用性

当创建复杂对象的算法应该独立于该对象的组成以及他们的装配方式时——对象的组成及装配方式是生成器的抽象接口来表示的。而对象的创建的算法是有生成器的子类实现的。

当构造过程必须允许被构造的对象有不同的表示时——也就是,同样的构造过程,可以生成不同的产品。这里是通过配置导向器的生成器的子类实例来实现的。

4、 结构

wps_clip_image-372

5、 参与者

l Builder:为创建一个product对象的各个部件指定抽象接口。这些部件最终构成了product,而对这些抽象接口的调用,则是装配product的操作,调用的次数,入参等不同,则最终生成的product也不同。product本身的构造过程可能会非常复杂。但是,这些复杂度对Director(导向者)是隐藏的。这些抽象的接口描述了产品的组成以及装配方式。

l ConcreteBuilder:实现Builder的接口以构造和装配产品的各个部件;定义并明确它所创建的表示;提供一个检索产品的接口。这个类实现了Builder的抽象接口,从而可以创建不同的表示,但是组成和装配过程还是一样的。

l Director:构造一个使用Builder抽象接口的对象。更对象根据Builder的接口来装配并生产产品。

l Product:表示被构造的复杂的对象。ConcreteBuilder创建该产品的内部表示,并定义它的装配过程;包含定义组成部件的类,包括将这些部件装配成最终产品的接口。

6、 协作

l 客户创建Director对象,并用它想要的Builder对象进行配置。

l 一旦产品不仅被生成,导向器就会通知生成器。

l 生成器处理导向器请求,并且将部件添加到该产品中。

l 客户从生成器中检索产品。

wps_clip_image-911

7、 效果

l 它使你可以改变一个产品的内部表示。Builder提供给Director的抽象接口可以使生成器隐藏这个产品的表示和内部结构。它同时也隐藏了该产品时如何装配的。因为产品时通过抽象接口构造的,你改变该产品的内部表示时,所要做的只是定义一个新的生成器。

l 它将构造代码和表示代码分开。Builder模式通过封装一个复杂对象的创建和表示方式提高了对象的模块性。每个ConcreteBuilder包含了创建和装配一个特定产品的所有代码。

l 它可以使你对构造过程进行更精细的控制。

8、 实现

l 通常一个抽象的Builder类为导向者可能要求创建的每一个构件定义一个操作。这些操作缺省什么也不做。一个ConcreteBuilder类对它有兴趣创建的构建重定义这些操作。

l 装配和构造接口:一般构造请求的结果只是被添加到产品中,特殊情况下,需要返回给导向器。

l 产品没有抽象类:一般他们没有公共的部分。如果有也可以设置一个抽象类。

l Builder中缺省的方法为空。

9、 代码示例

class MazeBuilder {

public:

    virtual void BuildMaze() { }//部件的构造方法

    virtual void BuildRoom(int room) { }

    virtual void BuildDoor(int roomFrom, int roomTo) { }

    virtual Maze* GetMaze() { return 0; }

protected:

    MazeBuilder();

};//MazeBuilder是生成器

class StandardMazeBuilder : public MazeBuilder {

public:

    StandardMazeBuilder();

/*

*/

    virtual void BuildMaze();

    virtual void BuildRoom(int);

    virtual void BuildDoor(int, int);

/*

*/

    virtual Maze* GetMaze();

private:

    Direction CommonWall(Room*, Room*);

    Maze* _currentMaze;

};//StandardMazeBuilder是ConcreteBuilder,提供部件的具体构造代码

Maze* MazeGame::CreateMaze (MazeBuilder& builder) {

    builder.BuildMaze();

    builder.BuildRoom(1);

    builder.BuildRoom(2);

    builder.BuildDoor(1, 2);

    return builder.GetMaze();

}//CreateMaze是导向器,调用生成器的抽象接口完成产品的构造过程。

//下面代码描述产品的构造过程

Maze* maze;//最终的产品

MazeGame game;//Director,导航者

StandardMazeBuilder builder;//ConcreteBuilder,实际的构造类

game.CreateMaze(builder);//开始装配

maze = builder.GetMaze();//获取装配后的产品

posted @ 2010-03-31 21:07 常高伟 阅读(154) | 评论 (0)编辑 收藏


2010年3月3日

1.1 ABSTRACT FACTORY 抽象工厂

1、 意图

提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。

2、 动机

“客户仅与抽象定义的接口交互,而不使用特定的具体类的接口。”

这里的主要的思想是封装对象的创建的过程。客户端可以不需要知道具体要创建那些对象,而只需要知道创建某一系列的对象所用到的“工厂对象”即可。

3、 适用性

一个系统要独立于它的产品的创建、组合和表示时。 

一个系统要由多个产品系列中的一个来配置时。 

当你要强调一系列相关的产品对象的设计以便进行联合使用时。 

当你提供一个产品类库,而只想显示它们的接口而不是实现时。 

4、 结构

wps_clip_image-278

5、 参与者

AbstractFactory:创建一系列对象的抽象类。

ConcreteFactory:实现具体创建产品对象的操作。

AbstractProduct:为一类产品对象声明一个接口。

ConcreteProduct:定义一个被相应的具体工厂创建的对象;实现AbstractProduct接口。

Client:仅使用AbstractFactory和AbstractProduct类声明的接口。

6、 协作

在运行时刻,创建一个ConcreteFactory实例,它创建具有特定实现的对象。为创建不同的对象,客户应使用不同的具体工厂。

AbstractFactory将具体对象的创建延迟到它的子类ConcreteFactory中。

7、 效果

1) 它分离了具体的类:一个工厂封装创建产品的责任和过程,它将客户和类的实现分离。客户通过抽象接口操作实例。产品的类名也在具体工厂实现中分离,他们不出现在客户代码中。

2) 它使得易于交互产品系列。

3) 它有利于产品的一致性。

4) 难于支持新的种类。

posted @ 2010-03-03 20:27 常高伟 阅读(117) | 评论 (0)编辑 收藏

1.1 设计模式怎样解决设计问题

1.1.1 寻找合适的对象

面向对象设计最困难的部分是将系统分解为对象的集合。

设计的许多对象来源于现实世界的分析模型,这里和领域驱动设计有点关联。分析所得到的类,很多事现实中并不存在的类。这是抽象的结果。设计中的抽象对于产生灵活的设计至关重要。就像我设计的一个流程调度模型。

1.1.2 决定对象的粒度

记笔记可以让我达到沉流的状态。

1.1.3 指定对象接口
1.1.4 描述对象实现

OMT表示法:

1、 对象:最上面的黑体表示类名,下面依次是操作,数据。

wps_clip_image-219

2、 实例化:虚线箭头表示一个类实例化另外一个对象。

wps_clip_image-245

3、 继承:竖线和三角表示继承关系。

wps_clip_image-263

4、 抽象类:类名以黑体斜体表示,操作也用斜体表示。

5、 引用

wps_clip_image-293

箭头加黑点表示一个类引用另外一个类。

重点:

1、 类的继承和接口继承的比较

对象的类和对象的类型的区别:

对象的类定义了对象是怎样实现的,同时也定义了对象内部状态和操作的实现。对象的类型只与它的接口有关。一个对象可以由多个类型(支持多个接口),不同类的对象可以有相同的类型。

类和类型紧密相连,类定义了对象的操作,也定义了对象的类型。

类的继承和接口的继承的差别:

c++中接口继承接近于公有继承纯抽象类。纯实现继承或纯类继承接近于私有继承。

2、 对接口编程,而不是对实现编程——面向对象设计的第一个原则

1.1.5 运用复用机制

1、 继承和组合的比较

继承是一种白箱复用,父类的内部细节对子类可见。

对象组合彼此不知道对方内部细节,成为黑箱复用。

继承的优缺点:

1) 子类可以直接重定义父类的操作。

2) 编译时刻决定了,无法在运行期间更改。

3) 子类要知道父类的实现细节,这样就部分破坏了封装性。子类和父类依赖过于紧密,父类的某些变化必然导致子类的变化。开发过程中遇到过类似的问题。这种依赖,限制了灵活性以及复用性。比如,服务体系中经常出现这样的问题,导致代码拷贝。

组合(通过获得对象的引用而在运行时刻动态的定义)的优缺点:

1) 对象间通过接口彼此交互。

2) 对象只能通过接口访问,不要也不能知道对方细节,这样不会破坏封装性。

3) 运行时刻可以使用另外一个对象替换这个对象,提高了灵活性。

4) 对象的实现基于接口编写,所以实现上存在较少的依赖关系。

5) 优先使用组合有助于保持每个类被封装,并被集中在单个任务上,提高整体内聚性。类和类的层次都维持一个较小的规模,

6) 基于对象组合的设计会有更多的对象(而又较少的类),且系统的行为依赖于对象间的关系而不是定义在某个类的内部。

理想的情况下,应该通过组合原有构件实现新的功能,而不是创建新的构件。

面向对象设计的第二个原则:优先使用对象组合,而不是类继承。

2、 委托

委托时一种组合方法,它是组合具有与继承同样的能力。

委托的主要优点在于它便于在运行时刻组合对象操作,以及更改操作的组合方式。它是软件更加的灵活。

和其他的技术方案相同,它也存在不足之处:增加了软件的复杂度——动态的,高度参数化的软件比静态的软件更难于理解。

3、 继承和参数化类型的比较

1.1.6 关联运行时刻的结构和编译时刻的结构
1.1.7 设计应支持变化

设计应该支持变化——所说的是,一个设计方案,对变化要有一定的适应性,即封装变化。

变化是导致重新设计的原因。设计要对一定范围内的变化友好。

4、 

对于程序的分层设计,对于处于同一分层的模块,对外应保持一定的抽象,并且,使用同种类型的通信协议。

posted @ 2010-03-03 20:26 常高伟 阅读(337) | 评论 (0)编辑 收藏

1.1 变量存储域

1.1.1 一个示例

pang123hui首先提供了一个网上流传的学习代码示例:

int a = 0; //全局区 

void main() 

{

int b; //栈 

char s[] = “abc”; //s在栈,abc在文字常量区 

char *p1,*p2; //栈 

char *p3 = "123456"; //123456在常量区,p3在栈上 

static int c =0; //全局区 

p1 = (char *)malloc(10); //p1在栈,分配的10字节在堆 

p2 = (char *)malloc(20); //p2在栈,分配的20字节在堆 

strcpy(p1, "123456"); //123456放在常量区 

}

这个代码示例中出现了“全局区”,“栈”,“文字常量区”,“堆”等词语。为了统一,我们使用《C专家编程》中的说法:堆栈段,BSS段,数据段,文本段。

各个段的作用如下:

1、 文本段:包含程序的指令,它在程序的执行过程中一般不会改变。

2、 数据段:包含了经过初始化的全局变量和静态变量,以及他们的值。

3、 BSS段:包含未经初始化的全局变量和静态变量。

4、 堆栈段:包含了函数内部声明的局部变量。

当然,上面段的作用不仅于此,具体的作用会在下面的知识点中介绍。

1.1.2 通过代码测试变量的存储位置

Linux下可以通过系统命令“size”查看可以执行程序各个段的大小。但是,可执行程序中的段结构和运行中程序在内存中的段结构并不完全相同,但是有一定的映射关系。具体如下图所示(图片信息来自《C专家编程》):

wps_clip_image-696

下面通过代码示例和“size”来研究变量的存储区域。

test.c

int main()

{

return 1;

}

编译,并且查看可执行程序各个段的大小:

wps_clip_image-779

更改test.c:

int g_data;

int main()

{

return 1;

}

编译,并且查看可执行程序各个段的大小:

wps_clip_image-849

可以发现,文本段,数据段都没有发送变化,而BSS段增加了4个字节。

结论1:未初始化的全局变量保存在BSS段中

继续:

int g_data = 1;

int main()

{

return 1;

}

编译:

wps_clip_image-958

可以发现,BSS段和文本段相同,而数据段增加了4个字节。

结论2:经过初始化的全局变量保存在数据段中

继续:

int main()

{

static int g_data;

return 1;

}

编译:

wps_clip_image-1066

可以发现,文本段,数据段都没有发送变化,而BSS段增加了4个字节。

结论3:未初始化的静态变量保存在BSS段中

继续:

int main()

{

static int g_data = 1;

return 1;

}

编译:

wps_clip_image-1183

可以发现,BSS段和文本段相同,而数据段增加了4个字节。

结论4:经过初始化的静态变量保存在数据段中

继续:

int main()

{

int i_data = 1;

return 1;

}

编译:

wps_clip_image-1288

可以发现,BSS段和和数据段相同,而文本段增加了16个字节。局部变量会在执行的时候在堆栈段中生成,函数执行完毕后释放。

结论5:函数内部声明的局部变量保存在堆栈段中

继续:

const int g_data = 1;

int main()

{

return 1;

}

编译:

wps_clip_image-1430

把全局变量定义为“const”后,也许你会感到奇怪,怎么BSS段和数据段都没有发生变化,而文本段却增加了4个字节。

结论6:const修饰的全局变量保存在文本段中

那么,const的局部变量?

继续:

int main()

{

const int i_data = 1;

return 1;

}

编译:

wps_clip_image-1587

结论7:const修饰的局部变量保存在堆栈段中

继续:

char *pstr = "";

int main()

{

return 1;

}

编译:

wps_clip_image-1666

在做一下更改:

char *pstr = "123456789";

int main()

{

return 1;

}

编译:

wps_clip_image-1733

可以发现,前后数据段和BSS段大小均未发生变化,而文本段增加了9个字节。

结论8:字符串常量保存在文本段中

1.1.3 结论

1、 经过初始化的全局变量和静态变量保存在数据段中。

2、 未经初始化的全局变量和静态变量保存在BSS段。

3、 函数内部声明的局部变量保存在堆栈段中。

4、 const修饰的全局变量保存在文本段中,const修饰的局部变量保存在堆栈段中。

5、 字符串常量保存在文本段中。

1.1.4 扩展阅读

《C专家编程》第6章——详细介绍各个段的作用。

posted @ 2010-03-03 02:38 常高伟 阅读(207) | 评论 (0)编辑 收藏


2009年10月14日

读S计划的理念

自助、互助,共同进步!

 

读S计划的初衷

现在有很多学习方式,搜索引擎、论坛、博客,qq群等等,那么我这样的计划还有存在的必要么?这个计划的独特之处在哪里?

读S计划的独特之处不在于其学习内容和方式,学习的内容我们可以根据实际情况调整,可以由多个人同时引导多个学习方向,这些都不是程式化的,也不是重点。

读S计划的独特之处在于其理念:致力于形成一种有计划、有组织,强调互动、互助和共同进步的学习模式和氛围。读S计划不是一个单纯的兴趣小组,我们会按照计划不断的切换话题。读S计划也不是提问的好地方,在每个阶段,我们都有限定的讨论范围,所以无关的问题很容易被忽略。读S计划希望多数人以积极的心态参与进来,更多的讨论、研究和贡献您的想法,而不是被动的接受和看,我们不希望将学习计划做成课堂模式,我们希望做成项目的模式 。读S计划致力于将信息以信息本身进行聚合,而不是以人进行聚合,因为学习计划在不断推进,所以不存在永远的权威人士,但知识本身是客观、中立的、权威的。

 

共建新型技术社区

建立新型的、关系紧密的、以知识体系为轴、强调经验积累与分享的的技术社区。

 

更深入了解此计划,可以阅读:

《技术族谱 之 读S计划:让我们一起快乐学习》

《准备启动一个开源项目 - 技术族谱 - 先期利用Goolge云计算平台》

《关于“读S计划”的聊天记录:统一理念,确定目前的工作》

《授人以“鱼”,不如授人以“渔”,放弃一个目标,设定另一个目标》

期待你加入我们的群组:http://hi.csdn.net/space-mtag-tagid-37.html

posted @ 2009-10-14 21:43 常高伟 阅读(145) | 评论 (0)编辑 收藏


2009年10月12日

上面一篇文章大致描述了一下插件开发框架整体结构。这篇描述一下核心层的设计和实现。

至于核心层的设计,我想借鉴 一下微内核的思想。核心层只负责实现下面几个功能:

1、 插件的加载,检测,初始化。

2、 服务的注册。

3、 服务的调用。

4、 服务的管理。

插件的加载,检测,初始化

插件的加载利用linux共享库的动态加载技术。具体的方法可以看一下IBM网站的一篇资料《Linux 动态库剖析》

服务的注册

服务的注册与调用采用表驱动的方法。核心层中维护一个服务注册表。

//插件间交互消息类型
typedef enum __Service_Type
{
    Service_Max,
}Service_Type;
//插件用于和其他插件通信接口函数,由插件提供。
typedef PRsp_Ele_Stream (*PF_Invoke_Service_Func)(PReq_Ele_Stream pele_str);
//驱动表
typedef PF_Invoke_Service_Func Service_Drive_Table[Service_Max];

驱动表是一个数组,下标为插件间交互消息类型,成员为插件提供的接收的消息处理函数,由插件初始化的时候,调用插件框架的的注册函数注册到驱动表。

插件的初始化实现为:
//插件用于注册处理的消息类型的函数,由插件框架提供。
typedef RET_RESULT (*PF_Service_Register_Func)(Service_Type service_type);
//插件用于和其他插件通信接口函数,由插件框架提供。
typedef PRsp_Ele_Stream (*PF_Invoke_Service_Func)(PReq_Ele_Stream pele_str);
//插件回复响应函数。插件收到异步请求后,处理完成后,发送响应消息给请求的插件。由插件框架提供
typedef void (*PF_Send_Response_Func)(PRsp_Ele_Stream pele_str);
//初始化插件信息
typedef struct Plugin_Init_St
{
    PF_Service_Register_Func register_func;//服务注册函数,要注册一系列的枚举值。插件可以处理的服务枚举值
    PF_Invoke_Service_Func invoke_serv_func;//和其他组件交互时,调用的用于和其他组件交互的函数。发送请求消息。
    PF_Send_Response_Func send_rsp_func;//再设计一个回复响应消息的接口。收到异步请求后,处理完毕后通知请求模块处理结果。
} Plugin_Init_St, *PPlugin_Init_St;
//初始化插件函数,类似于构造函数。由插件提供,供插件框架加载插件时初始化插件使用。
void PF_Init_Plugin(PPlugin_Init_St pinit_info);

      插件在函数PF_Init_Plugin中调用函数register_func来注册插件要处理的消息类型。

服务的调用
//信元结构体
typedef struct Ele_St
{
    Ele_Tag tag;
    Ele_Length len;
    Ele_Value  value;
    PEle_St next;
}Ele_St, *PEle_St;
//请求消息,信元流格式。
typedef struct Req_Ele_Stream
{
    Plugin_ID src_id;//源插件id
    Service_Type req_type;//请求类型
    PEle_St ele;
} Req_Ele_Stream, *PReq_Ele_Stream;
//响应消息,信元流格式。
typedef struct Rsp_Ele_Stream
{
    Plugin_ID dest_id;//目的插件id
    Service_Type req_type;//响应对应的请求的类型。
    Execute_Result result;//记录执行结果
    Execute_Reason reason;//记录执行结果的原因
    PEle_St ele;
} Rsp_Ele_Stream, *PRsp_Ele_Stream;
//接收插件调用服务请求函数,由插件提供,入参为请求信元流。返回值为响应信元流,用于同步请求处理。
PRsp_Ele_Stream PF_Receive_Invoke_Proc(PReq_Ele_Stream pele_str);
//插件收到响应消息的处理入口函数,由插件提供。如此为响应信元流。
void PF_Receive_Rsponse_Porc(PRsp_Ele_Stream pele_str);

插件间的依赖关系是通过信元流来实现的。至于信元流的使用在我的另一篇博客《使用信元流(TLVStream)规范、简化模块(C/C++)间交互 》 中有描述。插件对外的接口都是统一的。

如果插件要和其他的插件通信,则调用PF_Init_Plugin函数的传递的服务调用接口: invoke_serv_func。插件框架根据信元流的类型,查找驱动表,找到对应的服务接收函数。插件用函数 PF_Receive_Invoke_Proc接受其他插件的请求,此函数是插件想插件框架主动注册到驱动表的。

如果服务时同步的,这直接通过此函数返回,返回的信息在响应信元流中。如果是异步的请求,这插件在处理完成后,通过 send_rsp_func函数来发送响应。

插件的卸载
//卸载插件时调用的函数,类似于析构函数。由插件提供,供插件框架卸载插件时调用。
void PF_Destroy_Func();

posted @ 2009-10-12 20:20 常高伟 阅读(1103) | 评论 (0)编辑 收藏

这几天为了设计插件开发框架,尝试用了一下发散思维来思考问题。中间看过依赖注入,AOP(面向方面编程),以及契约式设计等。虽然有些工具无法直接使用,但是这些思想还是可以借鉴的,比如依赖注入,契约式设计。至于AOP,和工具相关性较大,虽然思想不错,但是无法直接在C++中使用。

我设计的插件间的依赖不是通过接口实现的,而是通过插件间的数据(信元流)。而信元流的检测可以使用契约来检查。

插件开发框架的总体结构

结构图

微内核

1、 负责插件的加载,检测,初始化。

2、 负责服务的注册。

3、 负责服务的调用。

4、 服务的管理。

扩展层:

1、 日志的打印。

2、 消息(信元流)的解释,将二进制格式解释为文本。便于定位。

3、 消息和日志的追踪。

分布式处理层:

1、 用于和其他的框架通信。

2、 和其他的框架搭配,形成一个分布式的系统。

自动化测试框架层:

1、 集成 cppunit 。

2、 自动化集成测试框架。

3、 自动化功能测试框架。

和第三方框架集成层:

1 、和 第三方框架 集成层。

posted @ 2009-10-12 20:19 常高伟 阅读(666) | 评论 (0)编辑 收藏

原创  使用信元流(TLVStream)规范、简化模块(C/C++)间交互 收藏

  问题描述:
  在软件开发过程中,一般会对复杂的现实世界进行抽象,并且采用分而治之的策略,将大系统分解为子系统,将子系统分解为一个个的模块。模块间的通信一般采用函数调用的方式,这样会产生一些问题:
   1. 模块间的接口增多会导致模块间紧密耦合,不利于模块的重用、调试、维护。
   2. 接口函数参数会很复杂,容易出现很庞大,功能面面俱到的结构体。
   3. 不利于扩展,新增功能要新增接口函数,或者修改接口参数。


信元流的定义
  信元流是一种处理数据的方式。它对现实世界的数据进行抽象,形成一个一个的信元,然后由这些信元以一定的方式组合起来,形成信元流,来传递数据。下面信元流的实现。
  信元的实现
  信元由三部分组成,分别是信元标识(Tag),信元长度(Length),信元值(Value),即TLV格式。下面是一个信元的c定义:   
  typedef struct __ELEMENT_ST

     {

          ELE_TAG tag;

          ELE_LEN len;

          ELE_VALUE value;

     }ELEMENT_ST;

  信元流的实现:
  信元流分为两部分,信元流头部(head)和体部(body)。head部分用于记录信元流的整体描述,比如,信元流的起始模块,目的模块,以及信元流的消息类型等等。当然,也可以根据需要自己进行扩展。body部分包括信元流中信元的总大小,即ELE_MSG_BODY中body的长度。

        typedef struct __ELEMENT_STREAM_ST
        {
            MOD_ID src_mod;
            MOD_ID des_mod;
            ELE_MSG_TYPE type;
            ELE_MSG_BODY body;
        }ELEMENT_STREAM_ST;
        typedef struct __ELE_MSG_BODY
        {
            ELE_MSG_LEN len;
            char body[MAX_ELE_MSG_BODY_LEN];
        } ELE_MSG_BODY;

  构造信元流
  定义好结构体后,下面定义两个函数,分别向信元体中添加信元和删除信元。
   //pbody信元体的指针。这里没有使用信元流的指针是为了让函数的重用性更好,用户可以自己定义信元流。
        //tag:添加信元的TAG值,len:添加信元的长度。pvale:添加信元的值。
        int AddElementToStreamBody(ELE_MSG_BODY *pbody, ELE_TAG tag, ELE_LEN len, void *pvalue);
        //pbody信元体的指针。 //tag:获取信元的TAG值,buf_len:pbuf的长度。pbuf:目标缓冲区,要把信元VALUE的值写入吃缓冲区。
        int GetElementFromStreamBody(ELE_MSG_BODY *pbody, ELE_TAG tag, int buf_len, void *pbuf);
  信元流的body是一个缓冲区,信元在里面顺序排列。信元在body中顺序不影响它的获取。添加和获取的方法比较简单,不再赘述。


  一个信元流的实例
  下面来举一个具体的例子来介绍一下信元流的使用,以及它的优点。
  假如由两个模块A和B。A模块负责处理业务逻辑,B模块负责处理呼叫控制。A调用B的一个接口发起呼叫,接口如下。
        typedef struct __MAKE_CALL_ST
        {
            char caller_num[MAX_NUM_LEN];//主叫号码
            char called_num[MAX_NUM_LEN];//被叫号码
        }MAKE_CALL_ST;
        int MakeCall(MAKE_CALL_ST *pcall_info);

  后面需求有更改,某些情况下药携带主叫的callid信息。结构体会变成:
    typedef struct __MAKE_CALL_ST
    {
        char caller_num[MAX_NUM_LEN];//主叫号码
        char called_num[MAX_NUM_LEN];//被叫号码
        CALL_ID caller_callid;
    }MAKE_CALL_ST;
  某些情况下又需要携带主叫的SDP信息,结构体会变成:
    typedef struct __MAKE_CALL_ST
    {
        char caller_num[MAX_NUM_LEN];//主叫号码
        char called_num[MAX_NUM_LEN];//被叫号码
        CALL_ID caller_callid;
        SDP_INFO call_sdp;
    }MAKE_CALL_ST;
  随着需求的增加,这个结构体会越来越大,并且,其中的成员在某些情况下要使用,某些情况下又不使用,造成模块间的冗余数据。
  当然,你也可以采用其他的方法,比如,再多定义几个接口和结构体。但是这样的话接口和结构体的数量会呈爆炸式的增长。
  使用信元流可以很好的解决这个问题。把号码,callid,sdp等全部定义成信元,需要的时候塞进去,不需要的话就不添加。另外还有一个好处就是,一个模块可以对外只公布一个接口,来收取信元流,然后在根据信元流的类型进行分别处理。这样,一个模块对外就只有一个接口了,模块间的耦合性会降低。


一点改进
  上面定义的信元流的格式在实际使用的过程中还是碰到了一些问题,最突出的就是,信元流的大小是固定死的。这种情况下,如果信元信息很小,会导致空间浪费,效率降低;如果信元信息很多,信元流的空间又不够。

  可以对上面的这种方案进行一下优化,把信元的定义更改为:

  typedef struct __ELEMENT_ST

     {

          ELE_TAG tag;

          ELE_LEN len;

          ELE_VALUE value;

          ELEMENT_ST  *pnext_ele;//下一个信元流

     }ELEMENT_ST;

  将信元流的定义更改为:

        typedef struct __ELEMENT_STREAM_ST
        {
            MOD_ID src_mod;
            MOD_ID des_mod;
            ELE_MSG_TYPE type;
            ELEMENT_ST  *pfirst_ele;//第一个信元流
        }ELEMENT_STREAM_ST;

  将信元流和信元更改为动态申请的内存。这样既可以提高效率,有没有了大小的限制。

  需要增加两个接口,来申请和释放信元流。

  唯一不好的地方时,动态申请的内存需要程序员记得释放,否则会内存泄露。不过还有一个方法,即增加一个申请信元流的函数,如下
        ELEMENT_STREAM_ST *GetEleStream()
        {
            static     ELEMENT_STREAM_ST *pstream = NULL;
            if (NULL != pstream)
            {
                FreeEleStream(pstream);   
                pstream = NULL;
            }
            pstream = AllocteEleStream();
            return pstream;
        }

  这样的话,通过函数GetEleStream获取的信元流,只在函数范围内有效,退出函数后,立即无效。

posted @ 2009-10-12 20:18 常高伟 阅读(269) | 评论 (0)编辑 收藏

在这一系列的上一个文章中,介绍了构建C/C++插件开发框架的初步设想,下面我会一步步的向下展开,来实现我的这个设想。

今天主要谈一下我对这个框架的功能认识,或是期望。昨天看了一篇关于持续集成能力成熟度模型 的一篇文章,受此启发,我对此框架的认识渐渐清晰。

这个框架可以当做我们公司底层产品(交换机,资源服务器等)的基础设施。上层基于java开发的产品可以直接在OSGI上开发。

核心功能:

1、最重要的一个功能是,提供一个模块化的编程模型,促进模块化软件开发,真正的实现针对接口编程。

2、提供一个有助于提高模块可重用性的基础设施。

3、提供一个C/C++插件的运行环境。

4、提供一个动态插件框架,插件可以动态更改,而无需重启系统。这个功能虽然不难实现,但是用处好像不是很大。


扩展部分功能:

1、支持分布式系统结构,多个运行框架组合起来形成一个系统,对模块内部隐藏远程通讯细节。

2、支持系统的分层架构。

3、能够和其他的开发框架进行集成,比如OSGI,SCA等。

4、多个运行框架中,能够实现对运行框架的有效管理。

5、概念上要实现类似于SCA中component(构件),composite(组合构件),Domain(域)的概念。


开发部分功能:

1、为了简化开发,开发一个Eclipse插件,用于开发框架中的C/C++插件。能够根据插件开发向导,最终生成符合插件规范的公共代码,配置文件,Makefile文件等。


调试部分功能:

1、提供一个统一的日志处理函数,可以集成Log4cpp。

2、提供模块间的消息日志,以及框架对外的接口日志。

3、提供消息和日志的追踪功能,能将和某事件相关的消息和日志单独提取出来。

4、提供资源监测功能,监测对资源(内存,套接字,文件句柄等)的使用情况。


测试部分功能:

1、集成一些单元测试框架,比如unitcpp,达到自动化单元测试的目标。

2、自己实现自动化集成测试框架,并且开发相应的Eclipse插件,简化集成测试(利用脚本和信元流)。

3、集成原有的自动化功能测试框架flowtest,并且开发相应的Eclipse插件,简化功能测试。

4、实现性能测试,监测框架。


部署部分功能:

1、实现自动化部署。特别是在分布式应用的情况下。

2、提供一个命令行程序,通过命令更改系统配置,管理插件。

posted @ 2009-10-12 20:18 常高伟 阅读(754) | 评论 (0)编辑 收藏

 最近一直在学习OSGI方面的知识。买了一本《OSGI原理和最佳实践》,可是还没有到。遗憾的是,OSGI目前的几个开源框架只支持Java,对C和C++都不支持的。可惜我们公司目前主要的开发语言还是c和c++,即便是引进OSGI,所得的好处范围有限。而我对松散耦合的模块化开发向往已久。查了一下OSGI对C++支持的好像是有一个开源项目,不过好像应用范围很小。而SCA标准中是有对C++实现模型的支持的,但是几个开源的框架目前还只支持JAVA。

  昨天看了丁亮的转载的一篇博客《C/C++:构建你自己的插件框架 》,原文的链接:http://blog.chinaunix.net/u/12783/showart_662937.html 。看了一下里面讲的方法,自己倒是可以实现。所以有了构建自己的c/c++插件开发框架的想法。今天先写一下初步的设想。

C/C++插件开发框架的要素

  BlueDavy有一篇介绍服务框架要素的文章(链接:http://www.blogjava.net/BlueDavy/archive/2009/08/28/172259.html )。我的插件框架也要考虑、解决以下的几个问题:

  1、如何注册插件;

  2、如何调用插件;

  3、如何测试插件;

  4、插件的生命周期管理;

  5、插件的管理和维护;

  6、插件的组装;

  7、插件的出错处理;

  8、服务事件的广播和订阅(这个目前还没有考虑要支持);

  其中有几个点很重要:1)插件框架要能够使模块松散耦合,做到真正的面向接口编程;2)框架要支持自动化测试:包括单元测试,集成测试;3)简化部署;4)支持分布式,模块可以调用框架外的插件。

采用的技术
  插件框架要解决的一个问题就是插件的动态加载能力。这里可以使用共享库的动态加载技术。当然,为了简单,第一步只考虑做一个linux下的插件框架。

  总体结构

  框架的总体结构上,参考OSGI的“微内核+系统插件+应用插件”结构。这里要好好考虑一下把什么做在内核中。关于微内核结构,以前我做个一个微内核流程引擎,会在后面有时间和大家分享。

  框架中模块间的数据传送,有两种解决方法:一是普元采用的XML数据总线的做法。优点是扩展性好,可读性好。但是速度有些慢。二是采用我熟悉的信元流。优点的效率高,访问方便,但是可读性差一点,另外跨框架的数据传送,需要考虑网络字节序的问题。

  对于框架间的通信,通过系统插件封装,对应用插件隐藏通信细节。

      部署

      努力做到一键式部署。

posted @ 2009-10-12 20:16 常高伟 阅读(2235) | 评论 (0)编辑 收藏


仅列出标题  下一页

posts - 19, comments - 0, trackbacks - 0, articles - 0

Copyright © 常高伟