模式发现者
:
蔡超
北京天融信,软件架构师
SUN certified Enterprise Architect
Microsoft certified Solution Developer
IBM certified RUP Specialist
联系方式
:cai_chao@topsec.com.cn,chaocai2001@yahoo.com.cn
010-82776427
语境
在基于异步通信方式的系统中,实现模块的同步调用。
问题
消息队列已经成为目前很多软件选用的通讯方式,消息模式使得不同分布式组件间的耦合性较为松散,提高了系统的可维护性和可扩充性。但是有时我们希望在这种情况下能够模拟同步的调用方式,希望能够通过一个组件透明的帮我们实现这种异步和同步调用的转换。
解决方案
图表
1
基于消息的分布式系统
上图是一种常见的基于消息的分布系统结构,系统中包含两条队列。命令队列用于传送模块间相互调用的命令对象,响应队列用于传送命令处理后的响应结果对象。
在通常情况下消息队列上的模块会以异步的方式工作,这种情况下模块通常不关心命令执行后的状况,常常是不须返回值的。
为了模拟同步调用我们通过一个
SynProxy
组件来帮助我们完成由异步到同步调用的模拟。使得
Client
对其他模块的调好像是同步的一样。
SynProxy
组件的结构如下:
图表
2
SynProxy
的静态结构
SynProxy:
组织完成异步到同步调用的转换
WaitingQueue:
存放还未获得执行返回值的
Command
对象
UIDCreator:
产生能够唯一标示每个
Command
对象的
UID
ResponseReceiver:
监听响应队列中的响应消息,它在独立的线程中运行
Command
:命令对象
Response
:响应对象
图表
3
将异同步调用转化成同步调用的过程
部分解释:
(
以上以在
JAVA
环境中为例
)
Wait
表示调用
Command
的
wait
方法阻塞住当前线程
Command
执行模块在执行了响应操作后生产
Response
对象用于放回结果,
Response
对象中的
UID
属性应该与处理的
Command
的
UID
的值相同
Notify
表示调用
Command
的
notify
方法唤醒被阻塞的线程
上面过程中的
wait
设置了阻塞的超时时间(来自于
Command
对象的属性值)
蔡
超
SCEA
,
SCBCD
,
MCSD
北京天融信软件架构师
SUN,Microsoft培训中心特邀高端教师
常年提供架构咨询服务
chaocai2001@yahoo.com.cn
,
010-82776427
4+1
视图与
UML
软件架构设计已经逐渐成为现代软件开发过程的核心,然而能够清晰表明架构设计并不是一件容易的事,就面向对象开发而言,
RUP
的
4+1
视图已在架构设计的撰写中得到了广泛的应用和认可。
对于
4+1 view
的描述有几个不同版本(或包含的视图不同,或视图的名称不同),文中以
Philippe Kruchten, November 1995
提出的
4+1
视图为准。
4+1
视图包括:逻辑视图(
Logic View
),开发视图(
Develop View
),进程视图(
Process View
),物理视图(
Physical View
)和场景视图(
Scenarios
)。
视图间的关系
4+1
视图不仅便于我们记录架构设计,实际上它也指导了我们进行架构设计活动的部分过程。
通常我们选择
UML
来表现各种视图,以下列出了
UML
和各视图的对应关系
4+1
视图
UML
场景视图
use case
逻辑视图
类图
开发视图
类图,组件图
进程视图
无完全对应
部署视图
部署图
在架构设计稳定中通常不会给出较多的用例描述,这些是在需求稳定中定义。但是往往架构文档会选择一些用例,列入文档中,这些用例和一些非功能性需求一起用以证明架构的有效和正确性。在逻辑视图中用例的实现是必不可少的一节,尽管架构设计更关注非功能性需求。
融入
MDA
的思想
对于逻辑视图和开发视图所应包含的内容常常会觉得很难区分两者间的明显界限。逻辑视图包含更多的分析模型与实现技术本身相关性应该较少,如业务对象模型及其扩展。而开发视图则会与实现技术紧密相关。
随着
MDA
思想的推广,在架构设计文档的撰写方面也产生了影响,我们不难把
MDA
的
PIM
和逻辑视图联系起来,而把
MDA
中的
PSM
和开发视图联系起来。
在编写逻辑视图是我们应该描述与技术平台无关的模型,而开发视图则描述与实现技术平台相关的模型。
如在逻辑视图中表现的某些实体类,我们会在开发视图中转换为
EJB
组件(实体
Bean
)。
这种做法不仅有利于我们编写架构设计文档,同时更是一种好的架构设计思考流程。
关于 MVC 模式中的通知机制
蔡超
1 MVC 简介
一般应用中用户界面的变化相对较为频繁 , 有时需要支持多种表现模式(如 WEB 客户端和 GUI 客户端),但是数据和业务逻辑相对保持稳定。
MVC(Model-View-Controller) 模式是一种常用的设计模式 ,MVC 将模型 , 显示和控制进行了分离,可以使得应用更加方便实现对多种表现模式的支持及降低表现形式修改对整体系统的影响。由于本文重点讨论的是 MVC 中的通知机制,至于 MVC 的其它内容可以参考其它相关文档。
图表 1 MVC 模式
2 MVC 的通知机制
上图是在 SUN 的 J2EE BluePrints 中关于 MVC 模式的描述 , 在实现 MVC 模式时首先应该注意的是模型与视图之间的关系。在这些关系中尤其值得大家注意的是模型通知视图,如果不能正确的设计这个通知机制(模型与视图之间关联实现通知)便会完全违背 MVC 的设计初衷。 MVC 模式的其中一个目的在于使模式独立与视图,然而不正确的理解和设计通知机制会导致模型和试图的依赖性。
2.1 采用 Observer 模式实现通知机制
既要实现模式到视图的通知机制,同时有要确保实现模型与视图的分离。通常我们可以通过Observer模式来实现这样的通知机制。
图表2 Observer模式
视图实现 Observer 接口,并向模型注册,模型通过调用所维护的观察者的实例调用 Update 方法来通知视图进行刷新。可见, Observer 接口有效的实现了模型和视图间的耦合性的分离。
2.2 模型通知视图还是控制器通知视图
图表 3 基于 MVC 的 J2EE 应用
在将 MVC 模式应用于的总体结构时,常常会有是模型通知视图还是控制器通知视图的问题,其实我认为这个问题完全取决于对系统各个部分的划分和理解,如果我们把模型层更多划分为数据实体 ( 如: Entity Bean) 则可能会发现,其实我们的通知机制是不能由模型部分来完成的,而是由我们的控制器来完成的。这种划分好象有些违背了 MVC 模式,但实事上特别是一些想要同时支持 B/S 和 C/S 的 J2EE 应用,控制器和模式通知机制常常有较大的耦合性( C/S 结构中,客户完全通过会话 Bean 来完成业务),可能有时在一起实现更好。
总之,更好的内聚性和更松散的耦合性才是架构设计的重点,应该做出适合自身应用的 MVC 架构。
3 结束语
MVC 模式的关键在于分离易变和不易变部分间的耦合性,所以在应用 MVC 模式时一定要注意解耦才是关键,同时一定要更据具体的使用环境进行调整,不要生搬硬套,如 Microsoft 的 MFC 采用的 Document-view 就是 MVC 的变体,它把控制器合并到视图中,这是因为考虑了视图与控制器紧耦合的影响。
【参考文献】
1. Sun Microsystem , J2EE BluePrints
2. GOF,Design Patterns, 机械工业出版社, 2002
作者: 蔡超
依赖注入(Dependency Injection)模式的特点分析与实现
――构造子注入(Constructor Injection)模式的分析与实现
蔡 超
(chaocai2001@yahoo.com.cn)
摘要:本文对IoC模式、依赖注入(Dependency Injection) 模式做了简要介绍,文中分析构造子注入模式与其他模式相比较的优势和特点,并给出了在JAVA中实现该模式的方法。
1 引言
IoC(Inversion of Control)模式以被目前的轻量级容器所广泛应用,通过IoC模式这些容器帮助开发者将来自不同项目的组件装配成一个内聚的应用程序。轻量级的IoC容器(如Spring、pico-container)虽然为我们的开发提供了很大的便利,但是在很多情况下这些轻量级容器所提供的功能并不一定非常适合我们的需要,也许这些容器的功能过于庞大了,或者所提供的功能缺乏对特定应用的针对性,或者我们需要更高的运行效率,这时我们可以在了解IoC的原理的基础上利用JAVA的反射机制自己实现灵活的、可扩展的组件机制。
2 IoC与依赖注入(Dependency Injection)模式简介
与GoF的设计模式相同,IoC模式同样是关注重用性,但与GoF模式不同的是IoC模式更加关注二进制级的重用性和可扩展性,即可以直接通过二进制级进行扩充,复用的模块通常被称为组件或者插件,组件和插件都是在运行时进行装载的。
GoF的设计模式中我们大量看到的是面向接口编程:Interface Driven Design 接口驱动,接口驱动有很多好处,可以提供不同灵活的子类实现,增加代码稳定和健壮性等等,但是接口一定是需要实现的,也就是如下语句迟早要执行:
AInterface a = new AInterfaceImp();
由于以上的代码被写入了调用者程序中,同时象AinterfaceImp这样的接口的实现类是在编译时被装载的,如果以后想加入新的接口实现类则必须修改调用者的代码。
IoC模式与以上情况不同,接口的实现类是在运行时被装载的,这样即使以后新添加了接口实现类是也不需修改调用者的代码(可以通过特定的方式来定位新增的实现类,如配置文件指定)。IoC英文为 Inversion of Control,即反转模式,这里有著名的好莱坞理论:你呆着别动,到时我会找你。
IoC模式可以延缓接口的实现,根据需要实现,有个比喻:接口如同空的模型套,在必要时,需要向模型套注射石膏,这样才能成为一个模型实体,因此,对于这些新生的容器,它们反转的是“如何定位插件的具体实现”。因此, Martin Fowler 给这种模式起了一个形象的名称“依赖注入”(Dependency Injection)。
图表 1 采用Dependency Injection前后的依赖关系变化
依赖注入的形式主要有三种,分别将它们叫做构造子注入(Constructor Injection)、设值方法注入(Setter Injection)和接口注入(Interface Injection)。
这三种方式在Martin Fowler的《Inversion of Control Containers and the Dependency Injection pattern》中都给出了详细的定义及说明,本文就不再赘述了,下面的内容将着重介绍构造子注入模式的特点及实现方法。
3 构造子注入模式的特点及实现
3.1 构造子注入模式的特点
通常情况下设值方法注入和接口注入较易于被开发人员接受,而构造子注入则应用较少,实际上构造子注入具有很多其他两者所不具有的优势:
1 构造子注入形成了一种更强的依赖契约
2 可以获得更加简明的代码
3 更加简明的依赖声明机制,无须定义XML配置文件或设值方法
4 更加符合接口与实现分离的组件特征,组件接口表明能够向其它组件提供的服务,而实现则应该是所提供服务的实现应该与服务契约无关(即不应包含用于获得依赖的设值方法等)。
5 不会出现不确定的状态。在设值方法注入中,由于并不是所有的设值方法(setter)都一定会被调用的,所以会有不确定状态。
从以上几点我们还可以分析出构造子注入对于组件代码的入侵性远小于其它两种模式(接口注入使得组件必须实现特定接口,设值方法同样要求组件提供特定的setter方法),代码更加易于维护。
图表 2 示例中类的关系
Client的实现依赖于接口A、B和C的实现,但是为了提供系统更好的灵活性和可扩展性,各接口的实现以组件的方式利用java的反射机制进行运行时装载,注意到组件间可能会存在某种依赖关系,例如组件AX依赖与接口B的实现类,而这中依赖关系必须在运行时动态注入,组件为了告诉组件的调用者这种依赖关系以便注入,可以使用上文提到的各种模式:
1 使用接口注入模式
public interface InjectB{
public void injectB(B bImp);
}
public interface InjectC{
public void injectC(C cImp);
}
public class AImp implements A,InjectB,InjectC{
…
public void injectB(B bImp);
public void injectC(C cImp);
…
}
2 使用设值注入模式
public class AImp implements A {
…
public void setB(B bImp);
public void setC(C cImp);
…
}
3 使用构造子注入模式
public class AImp implements A {
…
public AImp(B bImp, C cImp){
…
}
…
}
由以上实例可以清楚的看出采用构造子注入模式的实现组件代码最为简单,且所受的入侵性最小。
3.2 在JAVA中实现构造子注入模式
在java及.NET这样具有反射功能的语言中实现类型的运行时载入并不复杂,只要通过Class.forName或生成自己的ClassLoader就可以实现。
同样我们可以通过反射机制获取组件构造函数的参数,注入相应接口的实现,作者将此过程进行了封装,以下是代码:
public class RefectHelper {
public Object ConstructorHelper(String className,ConstructorParamDeal pd) throws Exception{
try{
//获取类中的构造函数
Constructor[] constructs=Class.forName(className).getConstructors(); //实现中默认使用第一个构造函数类创建实例
Class [] classes=constructs[0].getParameterTypes();
//获取要注入的参数实例
Object []obj=pd.dealParam(classes);
//创建实例
return constructs[0].newInstance(obj);
}catch(Exception e){
throw e;
}
}
}
/**
*构造函数参数注入
**/
public interface ConstructorParamDeal {
/**
*根据构造函数中参数的类型注入,相应的实现
@param classes 构造函数的参数类型
◎return 注入构造函数的参数实现
**/
public Object [] dealParam(Class [] classes);
}
public class ParamDeal implements ConstructorParamDeal{
/* (non-Javadoc)
* @see com.topsec.tsm.agent.helper.ConstructorParamDeal#dealParam(java.lang.Class[])
*/
public Object [] dealParam(Class[] classes) {
Object [] obj=new Object[classes.length];
for (int i=0;i<obj.length;i++){
//为不同类型注入选择不同实例
if (classes[i].equals(String.class)){
obj[i]=”Hello World”;
}
}
return obj;
}
}
上面的程序中ConstructorHelper用于利用反射机制枚举出载入类的构造函数及构造函数的参数的类型,至于不同类型注入什么样的实例则由ContructorParamDeal的实现者来决定,ContructorParamDeal的实现者同样可以以组件的形式在运行时动态载入。由于组件间的依赖关系的制约,所以组件实例化的顺序需要特别考虑。
4 结束语
三种依赖注入模式各有其特点和优势,只有充分理解这些模式间的不同,才能为自己的应用选择正确的依赖注入模式,文中介绍的构造子注入模式实现方法,在使用其他具有反射功能的语言(如:.NET)时同样可以参考。
[参考文献]
1 Martin Fowler,Inversion of Control Containers and the Dependency Injection pattern,http://www.martinfowler.com/articles/injection.html,2004
2 Erich Gamma,Design Patterns,Addison Wesley,1999
3 http://www.picocontainer.org/
4 彭晨阳,Ioc模式, http://www.jdon.com,2004