软件系统的可扩展性一般指系统、服务、模块能够完善、进化增加新的功能而又不影响已有的客户代码。其既要保证不变性,接口不变,不影响已有客户代码的运行、不需要重新编译;又要保持功能、特性的可变性,系统、模块自身内部可以升级、完善,增添新的功能。正如《敏捷软件开发-原则、模式与实践》(Robert C. Martin)一书半闭、半开原则(OCP)所讲:对于扩展是开放的(可以改变模块的性能、功能、其他特性);对于更改是封闭的(客户代码的更改是封闭的)。
系统、服务、模块的建模、设计过程中,扩展性主要来源于两个方面,可以预见的、可能扩展的环节、部分;以及不能预见的需要扩展的环节、部分。本部分主要说明可以预见的扩展及其处理手段。
通过对建模问题域的分析,一般可以确定系统、服务、模块的对外接口,并将可以预计到的要发生变化的部分定义为抽象方法,并利用Liskov替换原则进行子类化进行扩展。面向对象语言对于这种机制从编程语言本身予以支持。
常用的用子类化支持扩展的设计模式包括:模板方法(Template Method)、策略(Stragety)、命令(Command)。
Web应用开发框架Struts控制器ActionServlet在处理HTTP请求时,将请求处理转发给RequestProcessor类的process方法,该类可以子类化并在struts配置文件中进行配置使用。process方法体现模板方法,其实现如下:
public void process(HttpServletRequest request,
HttpServletResponse response) throws IOException,
ServletException {
// Wrap multipart requests with a special wrapper
request = processMultipart(request);
// Identify the path component we will use to select a mapping
String path = processPath(request, response);
if (path == null) {
return;
}
if (log.isDebugEnabled()) {
log.debug("Processing a '" + request.getMethod() +
"' for path '" + path + "'");
}
// Select a Locale for the current user if requested
processLocale(request, response);
// Set the content type and no-caching headers if requested
processContent(request, response);
processNoCache(request, response);
// General purpose preprocessing hook
if (!processPreprocess(request, response)) {
return;
}
this.processCachedMessages(request, response);
// Identify the mapping for this request
ActionMapping mapping = processMapping(request, response, path);
if (mapping == null) {
return;
}
// Check for any role required to perform this action
if (!processRoles(request, response, mapping)) {
return;
}
// Process any ActionForm bean related to this request
ActionForm form = processActionForm(request, response, mapping);
processPopulate(request, response, form, mapping);
if (!processValidate(request, response, form, mapping)) {
return;
}
// Process a forward or include specified by this mapping
if (!processForward(request, response, mapping)) {
return;
}
if (!processInclude(request, response, mapping)) {
return;
}
// Create or acquire the Action instance to process this request
Action action = processActionCreate(request, response, mapping);
if (action == null) {
return;
}
// Call the Action instance itself
ActionForward forward =
processActionPerform(request, response,
action, form, mapping);
// Process the returned ActionForward instance
processForwardConfig(request, response, forward);
}
如上代码所示,通过子类化可以定制、扩展RequestProcessor处理请求时的多个方面,如上传文件处理processMultipart、角色处理processRoles等。
由于子类化建立树型层次结构,并且在方法调用时实际调用的是子类的方法。如上面所作的子类化处理中,调用方法processRoles时实践调用的是子类的processRoles方法,因此可以理解为通过纵向(子类化)拦截方法调用,并将方法调用替换为子类的方法,从而提供服务的扩展。
职责链模式是另一种支持可预见扩展的另一种处理手段,类似的模式有流处理模式、过滤器模式、截取器模式等,但本质上都是将可以预见的变化部分独立出来,并定义一个处理流程,该处理流程中的处理环节可以予以扩展,并将多个处理环节组织在一起,各种模式各有其适用场合。都可以认为是在横向上截取方法调用,在调用之前、调用之后调用一定的方法,可能和该方法签名相同,职责链、流、过滤器都是如此,也可能不同,截取器就是这种例子。这些处理手段一方面要明确定义接口方法,另一方面要定义用于传送数据的结构体。由于是对可以预见的扩展支持,因此面向对象编程语言也都支持这些处理手段。
J2EE平台的Servlet规范中定义的Filter、FilterChain、FilterConfig及相关配置信息共同组成灵活的过滤器处理,用户可以定制Filter并可以配置在不同情况系统使用的不同的Filter进行数据流处理。
AOP是Aspect-Oriented Programming的简称,是另外一种支持横向截取的处理技术,这种技术相对上面的横向截取技术更加灵活、强大、更强的动态性,效率方面不如上面的处理技术。AOP不仅能够支持可以预见的扩展性,某些的AOP实现也能够支持不能预见的扩展性。
AOP实现方式有多种,一种是类似上面截取器模式一样定义各个方面的截取接口,这种方式不够灵活,但能满足一定的需要;一种是定义接口并通过Java语言的动态代理来实现接口方法的调用前和调用后截取,并加入需要的代码;更有甚至是通过对原有对象的字节码进行处理并加入扩展代码。
由于是对可预见扩展的支持,因此这类扩展主要应用于能多问题域充分分析的基础上定义接口和/或处理流程。很多开发框架如Struts、MFC、ACE等,都因为能够对所要解决的问题域能够有充分的分析,从而能够明确各构成部分的接口及允许的扩展。在这些框架中都存在纵向拦截和横向拦截。而横向拦截更适合于具有流程处理、多处理环节的情形。但正如MFC、Struts也都有其缺陷一样,当时的问题域充分的分析和技术的应用决定的框架,不能充分满足技术发展及应用开发环境变化需要,也就是说其不能预见的扩展,没有或者没有很好的予以处理,即架构的开放性、可扩展性还是不够。

本文探讨了软件系统可扩展性的概念,强调了在设计时既要保证不变性,又要允许功能的可变性。文章通过模板方法、策略、命令等设计模式,以及Struts框架中的ActionServlet和RequestProcessor的子类化来说明纵向拦截。同时,介绍了职责链模式、过滤器模式等横向拦截方法,以及J2EE的Filter机制。最后提到了AOP(面向切面编程)作为支持横向截取的更灵活技术,以及它在可预见和不可预见扩展中的应用。
10万+

被折叠的 条评论
为什么被折叠?



