利用Observer模式解决不同模块之间的交互
最近在做一个基于NetBeans Platform技术的Java客户端应用。在该应用中,有一系列的窗口组件(TopComponent),每一个窗口组件位于一个NetBeans Module中。这些Module是相互独立的,不能互相引用。这些不同的Module里面的各种窗口组件之间可能有各种交互行为。
以下面的业务场景来举例说明:
在第一个Module,WorkspaceModule中,有一个窗口组件WorkspaceTopComponent,双击打开其中的某一个Workspace(某种业务对象),就会引起另外两个Module中的若干窗口组件的变化:
在第二个Module,DocumentModule中,窗口组件DocumentTopComponent会显示打开的Workspace下的Document(某种业务对象)列表;
在第三个Module,JobModule中,窗口组件JobTopComponent会显示打开的Workspace下的Job(某种业务对象)列表。
等等诸如此类的交互行为。
一种做法是直接在WorkspaceModule中添加对DocumentModule和JobModule两个模块的引用,在WorkspaceTopComponent中直接操作DocumentTopComponent和JobTopComponent,这种做法导致了这些模块之间的紧密耦合,失去了作为Module存在的意义。
在Eclipse RCP中有一个比较爽的扩展点机制(Extension Point),可以用来轻松解决这种问题,在NetBeans中尚不清楚有没有类似的机制,因此采用Observer模式来尝试解决这种问题。
一、交互发起者和交互响应者接口
我们将发起交互的组件视为被观察者(Subject),对交互进行响应的组件视为观察者(Observer)。在标准JDK中已经提供了Observable类和Observer接口来提供这种机制,但是Observable是一个类,限制了它的使用。在我们的应用中,我们用下面两个接口来表示:
交互发起者接口ISponsor,它接受交互响应者的注册。
public interface ISponsor {
void register(IResponsor r);
void unregister(IResponsor r);
void unregisterAll();
}
交互响应者接口IResponsor,它要向感兴趣的交互发起者注册。
public interface IResponsor {
//这是一个标记接口,无方法
}
二、具体的交互发起者和具体的交互响应者接口
上面的两个接口是业务无关的,只提供了注册的机制,不涉及到具体的交互通知和响应。具体到业务,需要由具体的交互发起者和具体的交互响应者来提供。
在上面的业务场景中,WorkspaceTopComponent是一个具体的交互发起者,它会发起打开Workspace和关闭Workspace两种操作。为此提取出如下的接口:
public interface IWorkspaceSponsor extends ISponsor{
void notifyWorkspaceOpened(Workspace ws);
void notifyWorkspaceClosed(Workspace ws);
}
而DocumentTopComponent和JobTopComponent是上述两种操作的具体的交互响应者。为此提取出如下的接口:
public interface IWorkspaceResponsor extends IResponsor{
void workspaceOpened(Workspace ws);
void workspaceClosed(Workspace ws);
}
三、交互发起者注册表
在我们的应用中,可能有众多的交互发起者和交互响应者,各种交互响应者如何找到它所感兴趣的交互发起者,这是一个问题。我们提供一个交互发起者注册表(SponsorRegistry)的单实例对象来提供这种功能:
public class SponsorRegistry {
private Map<String,ISponsor> smap=new HashMap<String,ISponsor>();
private static SponsorRegistry registry=new SponsorRegistry();
private SponsorRegistry() {
}
public static SponsorRegistry getRegistry(){
return registry;
}
public void addSponsor(String sponsorName,ISponsor sponsor){
smap.put(sponsorName,sponsor);
}
public void removeSponsor(String sponsorName){
smap.remove(sponsorName);
}
public ISponsor getSponsor(String sponsorName){
return smap.get(sponsorName);
}
}
四、具体的交互发起者实现类和具体的交互响应者实现类
在上面的业务场景中,WorkspaceTopComponent实现IWorkspaceSponsor接口,并在组件创建时将自身注册到SponsorRegistry中。
//注:TopComponent是NetBeans Platform中提供的一种窗口界面组件,它继承自JComponent,下面的代码中改为继承JComponent也应该没问题。
public class WorkspaceTopComponent extends TopComponent implements IWorkspaceSponsor{
private List<IWorkspaceResponsor> responsors=new ArrayList<IWorkspaceResponsor>();
//下面是实现 ISponsor接口和IWorkspaceSponsor接口的相关方法
public synchronized void register(IResponsor r){
if(r instanceof IWorkspaceResponsor){
responsors.add((IWorkspaceResponsor)r);
}
else{
//...
}
}
public synchronized void unregister(IResponsor r){
if(r instanceof IWorkspaceResponsor){
responsors.remove((IWorkspaceResponsor)r);
}
else{
//...
}
}
public synchronized void unregisterAll(){
responsors.clear();
}
public void notifyWorkspaceOpened(Workspace ws){
synchronized(this){
Iterator<IWorkspaceResponsor> it=responsors.iterator();
while(it.hasNext())
it.next().workspaceOpened(ws);
}
}
public void notifyWorkspaceClosed(Workspace ws){
synchronized(this){
Iterator<IWorkspaceResponsor> it=responsors.iterator();
while(it.hasNext())
it.next().workspaceClosed(ws);
}
}
//当窗口创建时,将自身注册到SponsorRegistry中
public void componentOpened() {
SponsorRegistry.getRegistry().addSponsor("WorkspaceSponsor",this);
}
//其他界面和逻辑的实现代码略
//....
}
DocumentTopComponent和JobTopComponent均实现IWorkspaceResponsor接口,并在组件创建时从SponsorRegistry中找到名为WorkspaceSponsor的交互发起者,注册上去。
public class DocumentTopComponent extends TopComponent implements IWorkspaceSponsor{
public void workspaceOpened(Workspace ws){
//todo...
}
public void workspaceClosed(Workspace ws){
//todo...
}
//当窗口创建时,找到Sponsor并注册上去
public void componentOpened() {
ISponsor s=SponsorRegistry.getRegistry().getSponsor("WorkspaceSponsor");
s.register(this);
}
//当窗口关闭时,找到Sponsor并进行注销
public void componentClosed() {
ISponsor s=SponsorRegistry.getRegistry().getSponsor("WorkspaceSponsor");
s.unregister(this);
}
//其他界面和逻辑的实现代码略
//....
}
public class JobTopComponent extends TopComponent implements IWorkspaceSponsor{
public void workspaceOpened(Workspace ws){
//todo...
}
public void workspaceClosed(Workspace ws){
//todo...
}
//当窗口创建时,找到Sponsor并注册上去
public void componentOpened() {
ISponsor s=SponsorRegistry.getRegistry().getSponsor("WorkspaceSponsor");
s.register(this);
}
//当窗口关闭时,找到Sponsor并进行注销
public void componentClosed() {
ISponsor s=SponsorRegistry.getRegistry().getSponsor("WorkspaceSponsor");
s.unregister(this);
}
//其他界面和逻辑的实现代码略
//....
}
五、模块结构和依赖关系
根据以上的描述,上述业务场景现在由如下几个Module组成:
ObserverModule,提供交互发起者和交互响应者机制的的底层Module。
ISponsor接口、IResponsor接口位于此Module中,
SponsorRegistry类也位于此Module中。
BusinessModule,管理各种具体的交互发起者接口和交互响应者接口的Module。
IWorkspaceSponsor接口、IWorkspaceResponsor接口位于此Module中,
Workspace类等公共的业务对象也位于此Module中。
WorkspaceModule,Workspace业务功能所在模块。
WorkspaceTopComponent位于此Module中。
DocumentModule,Document业务功能所在模块。
DocumentTopComponent位于此Module中。
JobModule,Job业务功能所在模块。
JobTopComponent位于此Module中。
下图是以上模块的依赖关系示意图: