hello world

随笔 - 2, 文章 - 63, 评论 - 0, 引用 - 0
数据加载中……

【转】Netbeans Platform: Lookup! Lookup!

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:TopComponentNodeUtilities.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

posted on 2009-09-17 16:36 听风 阅读(1450) 评论(0)  编辑  收藏 所属分类: JAVA


只有注册用户登录后才能发表评论。


网站导航: