贝贝花花包包店,精品555皮具,钱夹,皮夹

字体: | 推荐给好友 上一篇 | 下一篇

ACE技术论文集-第2章 包装外观 Wrapper Facade

发布: 2008-6-13 15:00 | 作者: Prashant Jain and D | 来源: 转载 | 查看: 320次

2包装外观(Wrapper Facade):用于在类中封装函数的结构型模式
 
Douglas C. Schmidt
 
2.1介绍
  本论文描述包装外观模式。该模式的意图是通过面向对象(OO)类接口来封装低级函数和数据结构。常见的包装外观模式的例子是像MFC、ACE和AWT这样的类库,它们封装本地的OS C API,比如socket、pthreads或GUI函数。
  直接对本地OS C API进行编程会使网络应用繁琐、不健壮、不可移植,且难以维护,因为应用开发者需要了解许多低级、易错的细节。本论文阐释包装外观模式怎样使这些类型的应用变得更为简洁、健壮、可移植和可维护。
  本论文被组织如下:2.2详细描述使用西门子格式[1]的包装外观模式,2.3给出结束语。
 
2.2包装外观模式
 
2.2.1意图
  在更为简洁、健壮、可移植和可维护的较高级面向对象类接口中封装低级函数和数据结构。
 
2.2.2例子
  为阐释包装外观模式,考虑图2-1中所示的分布式日志服务的服务器。客户应用使用日志服务来记录关于它们在分布式环境中的执行状态的信息。这些状态信息通常包括错误通知、调试跟踪和性能诊断。日志记录被发送到中央日志服务器,由它将记录写到各种输出设备,比如网络管理控制台、打印机或数据库。
 
 
图2-1 分布式日志服务
 
  图2-1所示的日志服务器处理客户发送的连接请求和日志记录。日志记录和连接请求可以并发地在多个socket句柄上到达。各个句柄标识在OS中管理的网络通信资源。
  客户使用像TCP[2]这样的面向连接协议与日志服务器通信。因而,当客户想要记录日志数据时,它必须首先向日志服务器发送连接请求。服务器使用句柄工厂(handle factory)来接受连接请求,句柄工厂在客户知道的网络地址上进行侦听。当连接请求到达时,OS句柄工厂接受客户的连接,并创建表示该客户的连接端点的socket句柄。该句柄被返回给日志服务器,后者在这个句柄和其他句柄上等待客户日志请求到达。一旦客户被连接,它们可以发送日志记录给服务器。服务器通过已连接的socket句柄来接收这些记录,处理记录,并将它们写到它们的输出设备。
  开发并发处理多个客户的日志服务器的常见方法是使用低级C语言函数和数据结构来完成线程、同步及网络通信等操作。例如,图2-2演示怎样将Solaris线程[3]和socket[4]网络编程API用于开发多线程日志服务器。
 
 
 
图2-2 多线程日志服务器
 
  在此设计中,日志服务器的句柄工厂在它的主线程中接受客户网络连接。它随即派生一个新线程,在单独的连接中运行logging_handler函数、以处理来自每个客户的日志记录。下面的两个C函数演示怎样使用socket、互斥体和线程的本地Solaris OS API来实现此日志服务器设计。
 
// At file scope.
// Keep track of number of logging requests.
static int request_count;
// Lock to protect request_count.
static mutex_t lock;
// Forward declaration.
static void *logging_handler (void *);
// Port number to listen on for requests.
static const int logging_port = 10000;
 
// Main driver function for the multi-threaded
// logging server. Some error handling has been
// omitted to save space in the example.
int main (int argc, char *argv[])
{
struct sockaddr_in sock_addr;
 
// Handle UNIX/Win32 portability differences.
#if defined (_WINSOCKAPI_)
SOCKET acceptor;
#else
int acceptor;
#endif /* _WINSOCKAPI_ */
 
// Create a local endpoint of communication.
acceptor = socket (PF_INET, SOCK_STREAM, 0);
 
// Set up the address to become a server.
memset (reinterpret_cast(&sock_addr), 0, sizeof sock_addr);
sock_addr.sin_family = AF_INET;
sock_addr.sin_port = htons (logging_port);
sock_addr.sin_addr.s_addr = htonl (INADDR_ANY);
 
// Associate address with endpoint.
bind (acceptor,
reinterpret_cast
(&sock_addr),
sizeof sock_addr);
 
// Make endpoint listen for connections.
listen (acceptor, 5);
 
// Main server event loop.
for (;;)
{
thread_t t_id;
 
// Handle UNIX/Win32 portability differences.
#if defined (_WINSOCKAPI_)
SOCKET h;
#else
int h;
#endif /* _WINSOCKAPI_ */
 
// Block waiting for clients to connect.
int h = accept (acceptor, 0, 0);
 
// Spawn a new thread that runs the
//entry point and
// processes client logging records on
// socket handle.
thr_create (0, 0,
logging_handler,
reinterpret_cast(h),
THR_DETACHED,
&t_id);
}
 
/* NOTREACHED */
return 0;
}
 
logging_handler函数运行在单独的线程控制中,也就是,每个客户一个线程。它在各个连接上接收并处理日志记录,如下所示:
 
// Entry point that processes logging records for
// one client connection.
void *logging_handler (void *arg)
{
// Handle UNIX/Win32 portability differences.
#if defined (_WINSOCKAPI_)
SOCKET h = reinterpret_cast(arg);
#else
int h = reinterpret_cast(arg);
#endif /* _WINSOCKAPI_ */
for (;;)
{
UINT_32 len; // Ensure a 32-bit quantity.
char log_record[LOG_RECORD_MAX];
 
// The firstreads the length
// (stored as a 32-bit integer) of
// adjacent logging record. This code
// does not handle "short-s".
ssize_t n = recv
(h,
reinterpret_cast(&len),
sizeof len, 0);
 
// Bail out if we don’t get the expected len.
if (n <= sizeof len) break;
len = ntohl (len); // Convert byte-ordering.
if (len > LOG_RECORD_MAX) break;
 
// The secondthen reads
// bytes to obtain the actual record.
// This code handles "short-s".
for (size_t nread = 0;
nread < len;
nread += n)
{
n = recv (h, log_record + nread, len - nread, 0);
// Bail out if an error occurs.
if (n <= 0) return 0;
}
 
mutex_lock (&lock);
 
// Execute following two statements in a
// critical section to avoid race conditions
// and scrambled output, respectively.
++request_count; // Count # of requests received.
 
if (write (1, log_record, len) == -1)
// Unexpected error occurred, bail out.
break;
 
mutex_unlock (&lock);
}
 
close (h);
return 0;
}
 
注意全部的线程、同步及网络代码是怎样使用Solaris操作系统所提供的低级C函数和数据结构来编写的。
 
2.2.3上下文
  访问由低级函数和数据结构所提供服务的应用。
 
2.2.4问题
  网络应用常常使用2.2.2中所演示的低级函数和数据结构来编写。尽管这是一种惯用方法,由于不能解决以下问题,它会给应用开发者造成许多问题:
 
繁琐、不健壮的程序:直接对低级函数和数据结构编程的应用开发者必须反复地重写大量冗长乏味的软件逻辑。一般而言,编写和维护起来很乏味的代码常常含有微妙而有害的错误。
例如,在2.2.2的main函数中创建和初始化接受器socket的代码是容易出错的,比如没有对sock_addr清零,或没有对logging_port号使用htons[5]。mutex_lock和mutex_unlock也容易被误用。例如,如果write调用返回-1,logging_handler代码就会不释放互斥锁而跳出循环。同样地,如果嵌套的for循环在遇到错误时返回,socket句柄h就不会被关闭。
 
缺乏可移植性:使用低级函数和数据结构编写的软件常常不能在不同的OS平台和编译器间移植。而且,它们甚至常常不能在同一OS或编译器的不同版本间移植。不可移植性源于隐藏在基于低级API的函数和数据结构中的信息匮乏。
例如,在2.2.2中的日志服务器实现已经硬编码了对若干不可移植的本地OS线程和网络编程C API的依赖。特别地,对thr_create、mutex_lock和mutex_unlock的使用不能移植到非Solaris OS平台上。同样地,特定的socket特性,比如使用int表示socket句柄,不能移植到像Win32的WinSock这样的非Unix平台上;WinSock将socket句柄表示为指针。
 
高维护开销:C和C++开发者通常通过使用#ifdef在他们的应用源码中显式地增加条件编译指令来获得可移植性。但是,使用条件编译来处理平台特有的变种在各方面都增加了应用源码的物理设计复杂性[6]。开发者难以维护和扩展这样的软件,因为平台特有的实现细节分散在应用源文件的各个地方。
例如,处理socket数据类型的Win32和UNIX可移植性(也就是,SOCKET vs. int)的#ifdef妨碍了代码的可读性。对像这样的低级C API进行编程的开发者必须十分熟悉许多OS平台特性,才能编写和维护该代码。
由于有这些缺点,通过对低级函数和数据结构直接进行编程来开发应用常常并非是有效的设计选择。
 
2.2.5解决方案
  确保应用不直接访问低级函数和数据结构的一种有效途径是使用包装外观模式。对于每一组相关的函数和数据结构,创建一或多个包装外观类,在包装外观接口所提供的更为简洁、健壮、可移植和可维护的方法中封装低级函数和数据结构。
 
2.2.6结构
  包装外观模式的参与者的结构在图2-1中的UML类图中演示:
 
 
图2-1 包装外观模式的参与者的结构
 
包装外观模式中的关键参与者包括:
 
函数(Function):函数是现有的低级函数和数据结构,它们提供内聚的(cohesive)服务。
 
包装外观(Wrapper Fa?ade):包装外观是封装函数和与其相关联的数据结构的一个或一组类。包装外观提供的方法将客户调用转发给一或多个低级函数。
 
2.2.7动力特性
  图2-2演示包装外观模式中的各种协作:
 
 
图2-2 包装外观模式中的协作

TAG: 模式 通信软件 面向对象 Wrapper Facade

31/3123>
 

评分:0

我来说两句

seccode