最近在整理以前所看文章内容时,发现了几篇2003年程序员合订本里关于 ACE的文章,当年这几篇文章中所谈到的内容就让人非常兴奋,因为在那个国内热火朝天学习设计模式的几年里,这几篇文章因为其内容涉及到了bridge, strategy, adapter, facade等模式在这个网络通信框架中实打实的应用,让我切身体会到了面向对象设计模式的强大火力。同时因为这几篇文章绝不是我们在学习模式应用时想当然的东西,决不是简单的几句玩笑或不成熟的例子所能同日而语。所以我感觉还是把这几篇文章中的核心内容保存到网上比较好。
而这样做的目的有三:
一是给自己留了个备份,免得将来书在搬家或因为别的原因丢了。
二是也希望能有更多对设计模式感兴趣但却没看过这几篇文章的朋友不要有失之交臂的感觉。
三是让我感觉最有必要的一点就是有些个别站点居然凭借自己有这方面的论文内容去卖钱(手机充值方式),我想做为原作者马维达先生看了之后也会感到很不舒服。这也是让我感到“屎可忍而尿不可忍”的地方。
好了,说了一堆废话之后开始今天的正文。
在正文开始之前,有必要先解释一下什么是ACE,即
Adapter Communicate Envirment, "适配器通信环境",这是一个C++开发的网络通信框架,其自身是开源的。因为其诞生时间早(与Linux同一年出生),且其所提供的功能很好很强大,我们可以使用它完成可跨越多种平台完成通用的通信软件任务,其中包括:
事件多路分离和事件处理器分派、信号处理、服务初始化、进程间通信、共享内存管理、消息路由、分布式服务动态(重)配置、并发执行和同步等等。
且代码开发非常规范(大约100万行精心构造的C++代码),是一部不可多得的学习教材。该框架的创始人信息如下:
Douglas C. Schmidt,美国Vanderbilt大学电子工程与计算机科学系教授,ACE与TAO项目的创始人。(从美国海军到CERN物理实验室,从Boeing Advanced Avionics Systems到都能看到这两个软件的踪影) 他是中间件技术的先驱之一,在相关领域发表了百余篇学术论文。 包括Microsoft在内的许多公司都受到了他在通信软件技术方面的贡献的影响(国内也有一些网游公司使用该框架作为其通信机制实现平台)。
注:因为这篇文章不是面向初学者的文章,而是对那些有一定模式使用经验,同时也对上面所说的那几种设计模式有一定心得的朋友。相信当您看到本文中所说的几个模式的应用场景和最终的实现结果后,会有某些心得。
当然本文的内容大部分还是来自于马先生的文章,这里决不是抄袭,而是一种回顾,当然如果您感到本人很不耻,把别人的东西拿过来给自己帖金,那么还是请您不要再看了。因为我从没有感到这是在给我自己写,而是如上面的三个目的那样,所以当您看完本文后,不必说我的人品怎么怎么样,而是应该把注意力转移到模式的应用场景中去,结合自己的开发设计心得来进行回味,而这才是我写的目的。
首先介绍一下Bridge(桥模式)在ACE框架中的运用:
通信软件常常要对各种类型的事件进行多路分离、并进通行相应的处理。为使各种事件驱动的处理活动统一起来、并得以自动化,ACE提供了叫做ACE_Reactor的事件多路分离和事件处理器分派框架。同时,为了保证应用的可移植性,无论是在何种操作系统上,也无论底层使用了何种事件多路分离机制,ACE_Reactor所提供的接口都是一样的;并且,在有些平台上,还可以根据实际需要,在运行时更换底层所使用的事件多路分离机制(比如在Windows上既可以使用WaitForMultipleObjects(),也可以使用select())。Bridge模式正是ACE_Reactor具有这样的灵活性和可移植性的关键所在。
值得特别注意的是ACE_Reactor的私有成员reactor_impl_,这个指针变量正是ACE_Reactor中的“桥”实现的关键所在。ACE是具有高度可移植性的跨平台通信软件开发框架,在各种平台上提供相同的开发接口,是其重要目标之一。
上面所描述的ACE_Reactor接口及相关接口,在 ACE支持的各种平台上都是相同的,但因为这些平台提供的多路分离机制各不相同,所以在开发ACE_Reactor 时,必须为不同的平台提供不同的具体实现。要达到这一目的,一般的做法是为每种平台定义一个继承自 ACE_Reactor类的子类,在子类中提供该平台专用的具体实现。 但正如GoF在Design Pattern一书中所说,这样的继承把实现与抽象永久性地绑定在一起,使得我们难以独立地修改、扩展和复用各种抽象和实现。正是基于这样的考虑,ACE的开发者在实现ACE_Reactor时,采用了Bridge模式。
下图以ACE_Select_Reactor和ACE_WFMO_Reactor为例,说明了体现在ACE_Reactor中的Bridge模式:
我们可以对照Design Pattern一书中Bridge模式的结构图来理解上面这幅图。
Abstraction(ACE_Reactor)
定义ACE_Reactor抽象的接口,并维护有一个指针reactor_impl_,指向Implementor类型的一个对象。许多操作都将通过该指针转发给具体的Implementor对象。
RefinedAbstraction(无)
在ACE中没有定义RefinedAbstraction,但如果你想要对 ACE_Reactor接口进行扩展,就可以定义自己的。
Implementor(Reactor_Impl)
定义Reactor实现类的接口。
ConcreteImplementor(ACE_Select_Reactor和ACE_WFMO_Reactor)
实现Reactor_Impl接口,并定义其具体实现。除了这里提到的到的
ACE_Select_Reactor和
ACE_WFMO_Reactor,在ACE 中还定义了ACE_Dev_Poll_Reactor(基于“/dev/poll”或 “/dev/epoll”的Reactor实现)和ACE_TP_Reactor(支持基于线程池的事件分派)等其他类型的Reactor。详情可查阅ACE 的参考文档:[url]http://www.dre/vanderbilt.edu/Doxyen/Stable.[/url]
因为ACE_Reactor的实现采用了Bridge模式,我们在使用ACE_Reactor时拥有很大的灵活性。比如说,在Windows 平台上,系统提供的WaitForMultpleObjects()同时只能在64个句柄上等待,而ACE_WFMO_Reactor 在使用了2个句柄进行内部管理,所以如果你使用的是 ACE_WFMO_Reactor,实际上只能同时等待62个句柄,对于大型应用来说,这往往是不够的(显然,这个问题并非是Douglas C. Schmidt的错,而是Bill Gates的错)。如果你不需要使用ACE_WFMO_Reactor提供的一些特别的功能(比如等待同步事件),你可以改用ACE_Select_Reactor来进行事件多路分离:
ACE_Select_Reactor select_reactor;
ACE_Reactor reactor (&select_reactor);
... ...
通过使用面向对象技术及Bridge模式,ACE_Reactor具备了以下特性:
1.统一的OO多路分离和分派接口
2.自动进行事件处理器分派
3.支持透明的扩展
4.增加复用
5.增强类型安全性
6.改善可移植性
7.线程安全性
上面基本上是马维达原文中的内容:)
****************************************************************************
面要说的是
Strategy模式的应用.
ACE中包含有两种不同的内存管理类。一组基于ACE_Alloctor类,它们使用了动态绑定及Strategy模式来提供灵活性及可扩展性。其局限是它们只能用于进行局部动态内存管理。下面是ACE_Alloctor类的部分定义(C++):
class
ACE_Alloctor
{
public
:
ACE_Alloctor (
void
);
virtual
~
ACE_Alloctor(
void
);
virtual
void
*
malloc (size_t nbytes)
=
0
;
virtual
void
*
calloc (size_t nbytes,
char
initial_value
=
'
"0
'
)
=
0
;
virtual
void
*
calloc (size_t n_elem, size_t elem_size,
char
initial_value
=
'
"0
'
)
=
0
;
virtual
void
free (
void
*
ptr)
=
0
;
virtual
int
remove (
void
)
=
0
;
}
下面是继承自ACE_Alloctor的三种Alloctor(注:ACE_Cached_Allocator实际上继承自ACE_NEW_Alloctor。除这里列出的Alloctor以外,ACE还提供了其他Alloctor。详情参见ACE相关文档)如下:
ACE_Static_Allocator:
这个Alloctor 管理固定尺寸的内存。每次收到内存请求时,它都移动某个内部指针,并返回内存Chunk(大块)。它假定内存一经分配,就不会再被释放。
ACE_Cached_Allocator:
这个Alloctor预先分配一个内存池,其中含有一定数目的指定尺寸的chunk。这些chunk被维护在内部的一个freelist上,并在收到内存请(malloc())时返回。应用调用free()时,chunk返回内存的free list ,而不是OS.
ACE_New_Allocator:
包装C++ new和delete操作符的Alloctor,它在内部使用new和delete操作符来满足动态内存请求。
这三种Alloctor使用了一同的内存分配策略(strategy),适用于不同的情况。比如在实时系统中,有可能必须使用预先分配内存的ACE_Cached_Allocator,用以获取高性能和可预测性。因为这些ACE_Cached_Allocator都继承自ACE_Allocator,所以它们拥有相同的接口,
可以在编译时或运行时相互替换---但同时,因为这样的能力是通过虚函数获得的,它们也要付出相应的代价:额外的间接层次来的时间开销(本人注:可参见候捷先生的《c++对象模型深度探索》一书)。
好了,strategy模式应用到这里结束了。
**************************************************************************
下面接着说Adapter模式。
在前面的strategy模式中我们说到了ACE的内存管理类。其实在ACE中还有另外一组基于ACE_Malloc模板类的内存管理类。这一组类使用了C++模板和外部多态性来提供内存分配的灵活性。它们不仅能够管理局部动态内存,也能够管理进程间的共享内存。因为它们与基于ACE_Allocator的类不同,没有使用继承和动态绑定,所以性能更高。它们只能在编译时,通过其将要使用的内存池配置,而不能在运行时配置。显示,尽管其类效率更高,却不如ACE_Allocator 灵活。
下面是ACE_Allocator 模板类及其父类的部分定义:
template
<
ACE_MEM_POOL_1,
class
ACE_LOCK,
class
ACE_CB
>
class
ACE_Malloc_T
{
public
:
void
*
malloc (size_t nbytes);
void
*
calloc (size_t nbytes,
char
initial_value
=
'
"0
'
);
void
*
calloc (size_t n_elem, size_t elem_size,
char
initial_value
=
'
0"
'
);
};
template
<
ACE_MEM_POLL_1,
class
ACE_LOCK
>
class
ACE_Malloc:
public
ACE_Malloc_T
<
ACE_MEM_POLL_2, ACE_LOCK, ACE_Control_Block
>
{
public
:
ACE_Malloc (
const
ACE_TCHAR
*
pool_name
=
0
);
ACE_Malloc (
const
ACE_TCHAR
*
pool_name,
const
ACE_TCHAR
*
lock_name,
const
ACE_MEM_POOL_OPTIONS
*
options
=
0
);
};
如上所示,ACE_Malloc需要两个模板参数,一个是内存池类,一个是加锁类。而ACE中有以下几种不同的内存池。
1.ACE_MMAP_Memory_Pool
宏:ACE_MMAP_MEMORY_POOL
使用
<
mmap
(2)
>
创建内存池。内存因而可在内存间共享,在每次更新时内存被更新到backing store.
2.ACE_Lite_MMAP_Memory_Pool
宏:ACE_LITE_MMAP_MEMORY_POOL
使用
<
mmap
(2)
>
创建内存池。与前一种内存池不同,它不会更新到到backing store. 可靠性较低。
3.ACE_Sbrk_Memory_Pool
宏:ACE_SBRK_MEMORY_POOL
使用
<
sbrk
(2)
>
创建内存池。
4.ACE_Shared_Memory_Pool
宏:ACE_SHARED_MEMORY_POOL
使用SYSTEM V
<
shmget
(2)
>
调用创建内存池。内存可在进程间共享
5.ACE_Local_Memory_Pool
宏:ACE_LOCAL_MEMORY_POOL
通过C++ new 和 delete 操作符创建局部内存池。这种内存池不能在进程间共享。
因为这些内存池可能被多个进程或线程同时访问,所以还必须提供用于加锁的第二个模板参数。
现在考虑这种情况:ACE中大多数容器类都允许传入Allocator,用于管理容器中的内存。而这些内存分配方式只有基于ACE_Malloc 的类才能提供。如果要使用这些内存分配方式该怎么办呢?在已经拥有了ACE_Malloc的情况下,重写新的Allocator 肯定不是好的选择。而这时已出现了Adapter模式中所提到的应用场景中的两个,
即:
1.某个类有自己的接口(基于ACE_Malloc的类有自己的接口)。
2.客户期望的是另外的接口,与上面所说的接口不兼容(容器期望的是另一种接口,即ACE_Allocator所定义的接口,它与前者不兼容)。
显然需要编写一个Adapter类,把基于ACE_Malloc类的接口转换为容器类所期望的另一类接口。
而这正是ACE_Allocator_Adapter提供的功能。
typedef ACE_Allocator_Adapter
<
ACE_Malloc
<
ACE_SHARED_MEMORY_POOL, ACE_Nul_Mutex
>>
SMP_Allocator;
这个SMP_Allocator可以用于任何需要Allocator接口的地方。但其底层使用的却是拥有共享进程池(ACE_SHARED_MEMORY_POOL)的ACE_Malloc的功能。因为到这里我们可以推断出ACE_Static_Allocator,ACE_Cached_Allocator,ACE_New_Allocator 与 ACE_Allocator_Adapter 均继承自ACE_Allocator。而事实也确实是这样。
在这个应用场景中,Client是ACE容器类,Target是ACE_Allocator类。Adapter是ACE_Allocator_Adapter模板类。Adaptee是基于ACE_Malloc的类。Request是Malloc和Calloc等方法,而SpecificRequest()是基于ACE_Malloc的类的Malloc和Calloc等方法。而这即是Adapter Object中的类关系图,如下:
关于Adapter模式的应用就说到这里。
***************************************************************************
下面接着说一下Facade模式。
在ACE中,Facade(Wrapper Facade)模式的使用可谓举不胜数,这里仅以ACE Socket WrapperFacede 为例。在这些ACE类中(下面会进行说明)使用Facade模式,将“面向连接”的Socket API功能封装成了可移植的C++ 类。即将现有的非面向对象API所提供函数和数据封装到了简要,健壮,可移植,可维护的面向对象类的接口中。
而这些封装类包括:
ACE_Addr:ACE"网络地址"继承结构的根
ACE_INET_Addr:这个类封装了“Internet领域”地址簇
ACE_IPC_SAP:ACE IPC wrapper facade继承结构的根
ACE_SOCK:ACE Socket wrapper facade继承结构的根
ACE_SOCK_CONNECTOR:这是一个工厂,它链接至一个对等的接收者,然后在一个
ACE_SOCKET_Stream对象中初始化
ACE_SOCK_IO,ACE_SOCK_Stream:它们封装了数据模式socket支持的数据传输机制。
ACE_SOCK_Acceptor:这是一个工厂,它在一个ACE_SOCKET_Stream对象中初始化一个 新的通信端点,对“来自对等连接者的连接请求”做出响应。
当然通过这些类的封装提供了如下好处:
1.提高了类型安全,可以很快发现应用程序中很多微妙的类型错误,例如若用于“被动和主动建立连接”的工厂没有提供“发送和接收数据”的方法,那么,类型错误在编译时就可以发现,而不是运行时。
2.保证了可移植性,使用的是“和平台无关”的C++类。
3.简化了常见的使用情况,因为减少了应用程序代码量,节省了耗费在“低级网络编程细节”上的开发量,开发者可以将注意力庥中在高级的,“以应用为中心”的问题上。
4.另外还保留了效率,它通过使用内联函数,提高了软件质量,且没有牺牲性能。
好的,到这里,今天的内容差不多就要结束了,因为只是罗列内容,所以没什么太多的思考,只是一次回顾而已。
tag:ACE,Douglas C. Schmidt,马维达
作者:代震军,daizhj
原文链接:[url]http://www.cnblogs.com/daizhj/archive/2008/08/18/1270085.html[/url]