Netbeans Platform的Lookup类给我们带来了什么呢?注入依赖和解耦!Lookup的基本用法常常是:
Foo foo = someLookup.lookup(Foo.class)
使用Lookup来查找一个Foo类的实例。你可以将Lookup想成是一个Map,键是类,值是实例。
Netbeans中Lookup用途有:
1. 表现一个对象的能力。IS-A关系转变为HAS-A关系。
想
想传统的一种解耦方式:将其他模块创建的对象cast到特定的类型。Netbeans,不这么做,Netbeans做法是让其他模块创建的对象实现
Lookup.Provider接口(这个接口只有一个方法:public Lookup
getLookup()),然后通过这个Lookup获得你需要协同工作的对象。然后你的代码询问这个对象是否拥有特定接口的实例:
Your code <-- interact with --> Some object -- implements --> Lookup.Provider
|__ has a Lookup Object -- maintain
|__ Object 1
|__ Object 2
|__ Object 3 ...
这
样,通过Lookup, Some object 将 IS-A 关系(Some
object实现Object1接口,Object2接口,Object3接口)转化为HAS-A关系(Some
object的lookup对象拥有Object1, Object2, Object3).
这种转化具有重要的意义!方便!快捷!动态
(运行时无法改变实现的接口和类型)!弹性(运行时一个对象的能力是可能变化的,但是类型是不能变化的)!例如如果你想保存一个对象,你不必问其是否实现
Saveable接口,你只需要问其是否拥有一个SaveCookie实例(SaveCookie有一个save方法)。
2. 依赖注射和解耦
一个模块能够定义多个接口让其他模块实现这些接口,这个模块可以使用Lookup.getDefault()方法获得全局Lookup,然后根据全局Lookup查找其接口的所有实现。
3. 动态服务发现
模块能够非常简单的将代表全局服务或伪单例的对象通过默认的Lookup进行注射。
单
例模式的的目的是让一个对象只拥有一个实例。实现这种模式又很多方法,例如最简单的是创建一个工厂方法,然后将构造器定义为私有的,然后维护一个实例,对
所有的调用(工厂方法)都返回这个实例。单例模式在Netbeans中也有广泛的应用。通常全局服务没有必要有多个实例,例如在整个应用程序的主窗口中我
们只需要一个状态显式栏实例,来显式状态。例如Netbeans的Windows系统API,你不用直接使用它,这个模块
org.netbeans.core.windows将StatusDisplayer实现注射进来,放在默认的Lookup中,你仅仅使用
StatusDisplayer.getDefault().setStatusText("something").就可以设置状态信息了。
Lookup.Result 和 Lookup.Template
什么样的对象需要拥有自己的Lookup呢?
在Netbeans中的一些基本API类中又很多都有getLookup()方法,他们都实现了Lookup.Provider接口。Netbeans中有三个著名的例子:
1.
Project. Project API .
Project实际就是一个将一个目录和一个Lookup结合在一起,再加点东西的一个对象。Project API
定义了能在一个Project实例的Lookup中出现的(可选的)类。不同的模块实现了Project,提供自己的实现。其他API定义一些其他的类。
例如Java Project
API定义了一个ClassPathProvider的接口。这个接口能在Java源代码Project的Lookup中被找到,而其他普通的
Project没必要有这个接口。整个Project类定义如下:
public interface Project extends Lookup.Provider {
// 维护一个文件对象,代表项目目录
FileObject getProjectDirectory();
// 维护一个Lookup对象
Lookup getLookup();
}
可以从Project的Lookup中请求多个接口。例如ProjectInformation对象能够提供Project名称等基本信息。
2. TopComponent . 顶层组件是Netbeans的窗口系统管理的一个GUI面板。继承于TopComponent的类可以通过TopComponent.getLookup()方法来操作面板中的选择等事务。
3. Node. 节点就是通常说得树-节点类型的对象,代表底层的数据模型。例如项目和文件窗口中的文件树就是节点树。org.openide.nodes.Node就有getLookup()方法。
说了Netbeans中著名的三种具有Lookup对象的类,到底什么样的对象需要Lookup对象呢??
答案是,需要暴露某些能力的对象。这些对象可以通过Lookup对象向外界表明其具备某些能力,从而让其他代码能够使用这些对象的能力。举个例子,如果你想保存一个文件对象,那么这个文件对象肯定有保存的能力。传统方式怎么做呢:
public void actionPerformed (ActionEvent e) {
Object o = something.getSelection();
// 你会检查其是否实现Saveable接口
if (o instanceof Saveable && ((Saveable) o).canSave()) {
((Saveable) o).save();
}
}
可
惜的是这种方法太不强大了。Java对象无法轻易更改他们的类型。因为情况总是在变化的。Java文件包括源文件和编译文件,有些事情你无法在只有源文件
没有编译文件的情况下做,例如执行。并不是所有Java源文件都有对应的编译文件的。因此Lookup的出现能够很好的解决这种问题。Java文件的
Lookup内容是可以随时变化。当Java源文件被编译后,可以向Java文件的Lookup内容添加一个编译文件对象,如果Java文件被编译了,那
么Lookup内容中就保存一个编译文件对象,如果编译文件被删除了,这个编译对象也从Lookup内容中删除了。
public void actionPerformed (ActionEvent e) {
Lookup lkp = Utilities.actionsGlobalContext();
SaveCookie save = lkp.lookup (SaveCookie.class);
if (save != null) {
save.save();
}
}
事
实上,Netbeans中一个正在被编辑的文件的保存过程,就是代表这个文件的节点Node的Lookup中出现了一个SaveCookie对象。这个代
码并不需要知道SaveCookie.save()方法的具体细节,只需要知道SaveCookie对象在Lookup中,就可以保存。
Lookup是一种通讯机制
上
面说得Project就是一个例子,Netbeans中有一个概念叫服务提供者接口(Service Provider Interface,
SPI ). 不同的模块通过在默认的Lookup中安装ProjectFactory实例,来插入不同的项目类型project
type.你可以在Netbeans的项目向导中发现你可以创建不同类型的项目。Netbeans包含多个项目接口的实现。
Lookup和代理
ProxyLookup允许将两个Lookup融合。
Lookup和选择
那我们如何在程序中使用Lookup呢?
基本上说,我们无需自己编写Lookup(当然如果需要的话,你可以编写)。
NetBeans 的 Lookup 可以說是无处不在,它也是 NetBeans 中最基本的模块。在执行的時候,NetBeans 将Lookup分为系统服务池的Lookup和具有焦点窗口的对象池的Lookup。我们可以从以下地方获得Lookup:TopComponent 、Node 、Utilities.actionsGlobalContext()
、和 Lookups四个地方。
我
们通过Utilities.actionsGlobalContext()来获得对象池的Lookup,
这里Netbeans已经帮我们整合过Lookup对象了.Utilities.actionsGlobalContext()将返回一个Lookup对
象,这个对象是活动的(具有焦点)的顶层组件的Lookup的代理Lookup,如果顶层组件是Explorer视图的话,将代理被选择了的节点的
Lookup对象们。
我们通过Lookup.getDefault()方式获得系统服务池的Lookup.
Netbeans平台为我们准备好了几种使用方式:
1.
通过Lookups. 如果你需要包含固定数目对象的Lookup,你可以使用Lookups.fixed(Object...
objectsToLookup) (注意:... 代表可变参数数量),也可以使用Lookups.singleton(Object
objectToLookup)返回一个只包含一个对象的Lookup.
2.
AbstractLookup和InstanceContent.
如果你希望你的Lookup可以动态的包含对象(数目可变),那么你可以使用AbstractLookup和InstanceContent相结合的方
式。注意AbstractLookup并不是抽象类。具体的用法这里有个很好的例子,Fox的Lookup讲解:
在
这个例子中,有一个业务对象FoxObject,
有两个顶层组件:Consumer顶层组件和Producer顶层组件。Producer顶层组件中包含两个状态按钮
(ToggleButton):FoxButton和DonButton。Consumer顶层组件有一个List表单。
效果是:如果你
按下Producer顶层组件窗口中的其中一个Button,就会创建一个FoxObject对象,其名字根据按钮的名字命名,例如按下
DonButton将创建一个名字为Don的FoxObject.
如果程序的焦点在Producer顶层组件上(蓝色背景意味焦点在其上,灰色背景意味焦点不在其上)的话,Consumer顶层组件的List表单将出现
这个名字为Don的FoxObject,
当你再次按下DonButton时,按钮的状态改变,名字为Don的FoxObject被删除,List表单中相应的对象也消失了。如果你将程序焦点换到
Consumer顶层组件的时候,List表单中的所有对象都消失了,重新将焦点换到Producer顶层组件上时,List表单中的对象重新出现了。
这是怎么实现的呢?我们看一下:
1. 我们在Producer顶层组件中维护一个InstanceContent对象,两个FoxObject对象。在Producer顶层组件构造的时候,我们初始化Lookup:
private void initLookup(){
// 创建InstanceContent,然后构建一个AbstractLookup,然后将这个Lookup和Producer顶层组件关联
m_InstancePool=new InstanceContent();
this.associateLookup(new AbstractLookup(m_InstancePool));
}
在ToggleButton状态变化方法中,我们根据Button的状态,决定InstanceContent的内容(名为Don的FoxObject对象的添加和删除)
private void m_PutDonItemStateChanged(java.awt.event.ItemEvent evt) {
if(evt.getStateChange()==ItemEvent.SELECTED){
if(m_Don==null){
m_Don=FoxObject.createInstance("don", "Don, Chen");
}
m_InstancePool.add(m_Don);
}else if(evt.getStateChange()==ItemEvent.DESELECTED){
if(m_Don!=null){
m_InstancePool.remove(m_Don);
}
}
}
2. 我们让Consumer顶层组件实现LookupLisenter, 因为他要根据FoxObject对象的状态来显式List表单。他维护一个JList表单以及一个Lookup.Result结果。
final class ConsumerTopComponent extends TopComponent implements LookupListener {...
当Consumer顶层组件被打开和关闭时:
public void componentOpened() {
clearList();
// 初始化LookupQuery
initLookupQuery();
FoxService oService=(FoxService) Lookup.getDefault().lookup(FoxService.class);
if(oService !=null ){
oService.saySomething();
}
}
public void componentClosed() {
// 关闭LookupQuery
uninitLookupQuery();
clearList();
}
我
们看一下,在Consumer顶层组件窗口被打开时,我们根据FoxObject类制作一个模版,然后通过
Utilities.actionsGlobalContext()查找到所有FoxObject的实例,以Lookup.Result方式展现。找到这
些FoxObject实例后,对代表他们的Lookup.Result添加监听器,就是Consumer顶层组件自己。然后更新结果列表。
private void initLookupQuery(){
Lookup.Template oFoxTemplate=new Lookup.Template(FoxObject.class);
m_LookupResult=Utilities.actionsGlobalContext().lookup(oFoxTemplate);
m_LookupResult.addLookupListener(this);
refreshResultList();
}
private void uninitLookupQuery(){
if(m_LookupResult!=null){
m_LookupResult.removeLookupListener(this);
m_LookupResult=null;
}
}
实现Lookup监听器的方法:
public void resultChanged(LookupEvent ev){
refreshResultList();
}
一旦Lookup变化了,resultChanged方法就被调用,执行refreshResultList方法。这个方法获得Lookup.Result中的所有实例,如果有实例的话,重新根据这些实例绘制Consumer顶层组件的List表单。
private void refreshResultList(){
Collection<FoxObject> cList=m_LookupResult.allInstances();
if(!cList.isEmpty()){
this.m_FoxObjectList.setListData(new Vector<FoxObject>(cList));
this.m_FoxObjectList.revalidate();
this.m_FoxObjectList.repaint();
}else{
clearList();
}
}
整个演示就完成了。这里我们特别要注意几点:
1. FoxObject是业务对象,它仅仅关注自己的业务和信息,例如名字,ID等,对其他的事情一概不知。
2. Producer顶层组件需要和一个Lookup关联,这个Lookup是AbstractLookup和InstanceContent共同完成的,以便Lookup内的对象数目可以动态改变(通过ToggleButton的状态):
ProducerTopComponent --> AbstractLookup --> InstanceContent --> FoxObject Class
|
|__ Add/Remove --> Fox FoxObject <--> Button1
|
|__ Add/Remove --> Don FoxObject <-->
Button2
|
| 关键:Netbeans通过Utilities.actionsGlobalContext将
| Producer顶层组件和Consumer顶层组件解耦,相互通过Lookup沟通
ConsumerTopComponent --> Utilities.actionsGlobalContext().lookup(FoxObject.class)
|______ 监听 --> |__ Lookup.Result
|______ 更新 --> JList (根据Lookup.Result的变化)
3. 结构图
<-------------- FoxObject --------------->
|
| |
| <----- Lookup (Utilitis, AbstractLookup, InstanceConent) ------> |
| |
| |
ConsumerTopComponent XXX ProducerTopComponent
转自http://alarnan.spaces.live.com/blog/cns!819cbc613de169ef!141.entry