Netbeans Platform的Lookup類給我們帶來了什么呢?注入依賴和解耦!Lookup的基本用法常常是:
Foo foo = someLookup.lookup(Foo.class)
使用Lookup來查找一個Foo類的實(shí)例。你可以將Lookup想成是一個Map,鍵是類,值是實(shí)例。
Netbeans中Lookup用途有:
1. 表現(xiàn)一個對象的能力。IS-A關(guān)系轉(zhuǎn)變?yōu)镠AS-A關(guān)系。
想
想傳統(tǒng)的一種解耦方式:將其他模塊創(chuàng)建的對象cast到特定的類型。Netbeans,不這么做,Netbeans做法是讓其他模塊創(chuàng)建的對象實(shí)現(xiàn)
Lookup.Provider接口(這個接口只有一個方法:public Lookup
getLookup()),然后通過這個Lookup獲得你需要協(xié)同工作的對象。然后你的代碼詢問這個對象是否擁有特定接口的實(shí)例:
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 關(guān)系(Some
object實(shí)現(xiàn)Object1接口,Object2接口,Object3接口)轉(zhuǎn)化為HAS-A關(guān)系(Some
object的lookup對象擁有Object1, Object2, Object3).
這種轉(zhuǎn)化具有重要的意義!方便!快捷!動態(tài)
(運(yùn)行時無法改變實(shí)現(xiàn)的接口和類型)!彈性(運(yùn)行時一個對象的能力是可能變化的,但是類型是不能變化的)!例如如果你想保存一個對象,你不必問其是否實(shí)現(xiàn)
Saveable接口,你只需要問其是否擁有一個SaveCookie實(shí)例(SaveCookie有一個save方法)。
2. 依賴注射和解耦
一個模塊能夠定義多個接口讓其他模塊實(shí)現(xiàn)這些接口,這個模塊可以使用Lookup.getDefault()方法獲得全局Lookup,然后根據(jù)全局Lookup查找其接口的所有實(shí)現(xiàn)。
3. 動態(tài)服務(wù)發(fā)現(xiàn)
模塊能夠非常簡單的將代表全局服務(wù)或偽單例的對象通過默認(rèn)的Lookup進(jìn)行注射。
單
例模式的的目的是讓一個對象只擁有一個實(shí)例。實(shí)現(xiàn)這種模式又很多方法,例如最簡單的是創(chuàng)建一個工廠方法,然后將構(gòu)造器定義為私有的,然后維護(hù)一個實(shí)例,對
所有的調(diào)用(工廠方法)都返回這個實(shí)例。單例模式在Netbeans中也有廣泛的應(yīng)用。通常全局服務(wù)沒有必要有多個實(shí)例,例如在整個應(yīng)用程序的主窗口中我
們只需要一個狀態(tài)顯式欄實(shí)例,來顯式狀態(tài)。例如Netbeans的Windows系統(tǒng)API,你不用直接使用它,這個模塊
org.netbeans.core.windows將StatusDisplayer實(shí)現(xiàn)注射進(jìn)來,放在默認(rèn)的Lookup中,你僅僅使用
StatusDisplayer.getDefault().setStatusText("something").就可以設(shè)置狀態(tài)信息了。
Lookup.Result 和 Lookup.Template
什么樣的對象需要擁有自己的Lookup呢?
在Netbeans中的一些基本API類中又很多都有g(shù)etLookup()方法,他們都實(shí)現(xiàn)了Lookup.Provider接口。Netbeans中有三個著名的例子:
1.
Project. Project API .
Project實(shí)際就是一個將一個目錄和一個Lookup結(jié)合在一起,再加點(diǎn)東西的一個對象。Project API
定義了能在一個Project實(shí)例的Lookup中出現(xiàn)的(可選的)類。不同的模塊實(shí)現(xiàn)了Project,提供自己的實(shí)現(xiàn)。其他API定義一些其他的類。
例如Java Project
API定義了一個ClassPathProvider的接口。這個接口能在Java源代碼Project的Lookup中被找到,而其他普通的
Project沒必要有這個接口。整個Project類定義如下:
public interface Project extends Lookup.Provider {
// 維護(hù)一個文件對象,代表項(xiàng)目目錄
FileObject getProjectDirectory();
// 維護(hù)一個Lookup對象
Lookup getLookup();
}
可以從Project的Lookup中請求多個接口。例如ProjectInformation對象能夠提供Project名稱等基本信息。
2. TopComponent . 頂層組件是Netbeans的窗口系統(tǒng)管理的一個GUI面板。繼承于TopComponent的類可以通過TopComponent.getLookup()方法來操作面板中的選擇等事務(wù)。
3. Node. 節(jié)點(diǎn)就是通常說得樹-節(jié)點(diǎn)類型的對象,代表底層的數(shù)據(jù)模型。例如項(xiàng)目和文件窗口中的文件樹就是節(jié)點(diǎn)樹。org.openide.nodes.Node就有g(shù)etLookup()方法。
說了Netbeans中著名的三種具有Lookup對象的類,到底什么樣的對象需要Lookup對象呢??
答案是,需要暴露某些能力的對象。這些對象可以通過Lookup對象向外界表明其具備某些能力,從而讓其他代碼能夠使用這些對象的能力。舉個例子,如果你想保存一個文件對象,那么這個文件對象肯定有保存的能力。傳統(tǒng)方式怎么做呢:
public void actionPerformed (ActionEvent e) {
Object o = something.getSelection();
// 你會檢查其是否實(shí)現(xiàn)Saveable接口
if (o instanceof Saveable && ((Saveable) o).canSave()) {
((Saveable) o).save();
}
}
可
惜的是這種方法太不強(qiáng)大了。Java對象無法輕易更改他們的類型。因?yàn)榍闆r總是在變化的。Java文件包括源文件和編譯文件,有些事情你無法在只有源文件
沒有編譯文件的情況下做,例如執(zhí)行。并不是所有Java源文件都有對應(yīng)的編譯文件的。因此Lookup的出現(xiàn)能夠很好的解決這種問題。Java文件的
Lookup內(nèi)容是可以隨時變化。當(dāng)Java源文件被編譯后,可以向Java文件的Lookup內(nèi)容添加一個編譯文件對象,如果Java文件被編譯了,那
么Lookup內(nèi)容中就保存一個編譯文件對象,如果編譯文件被刪除了,這個編譯對象也從Lookup內(nèi)容中刪除了。
public void actionPerformed (ActionEvent e) {
Lookup lkp = Utilities.actionsGlobalContext();
SaveCookie save = lkp.lookup (SaveCookie.class);
if (save != null) {
save.save();
}
}
事
實(shí)上,Netbeans中一個正在被編輯的文件的保存過程,就是代表這個文件的節(jié)點(diǎn)Node的Lookup中出現(xiàn)了一個SaveCookie對象。這個代
碼并不需要知道SaveCookie.save()方法的具體細(xì)節(jié),只需要知道SaveCookie對象在Lookup中,就可以保存。
Lookup是一種通訊機(jī)制
上
面說得Project就是一個例子,Netbeans中有一個概念叫服務(wù)提供者接口(Service Provider Interface,
SPI ). 不同的模塊通過在默認(rèn)的Lookup中安裝ProjectFactory實(shí)例,來插入不同的項(xiàng)目類型project
type.你可以在Netbeans的項(xiàng)目向?qū)е邪l(fā)現(xiàn)你可以創(chuàng)建不同類型的項(xiàng)目。Netbeans包含多個項(xiàng)目接口的實(shí)現(xiàn)。
Lookup和代理
ProxyLookup允許將兩個Lookup融合。
Lookup和選擇
那我們?nèi)绾卧诔绦蛑惺褂肔ookup呢?
基本上說,我們無需自己編寫Lookup(當(dāng)然如果需要的話,你可以編寫)。
NetBeans 的 Lookup 可以說是無處不在,它也是 NetBeans 中最基本的模塊。在執(zhí)行的時候,NetBeans 將Lookup分為系統(tǒng)服務(wù)池的Lookup和具有焦點(diǎn)窗口的對象池的Lookup。我們可以從以下地方獲得Lookup:TopComponent 、Node 、Utilities.actionsGlobalContext()
、和 Lookups四個地方。
我
們通過Utilities.actionsGlobalContext()來獲得對象池的Lookup,
這里Netbeans已經(jīng)幫我們整合過Lookup對象了.Utilities.actionsGlobalContext()將返回一個Lookup對
象,這個對象是活動的(具有焦點(diǎn))的頂層組件的Lookup的代理Lookup,如果頂層組件是Explorer視圖的話,將代理被選擇了的節(jié)點(diǎn)的
Lookup對象們。
我們通過Lookup.getDefault()方式獲得系統(tǒng)服務(wù)池的Lookup.
Netbeans平臺為我們準(zhǔn)備好了幾種使用方式:
1.
通過Lookups. 如果你需要包含固定數(shù)目對象的Lookup,你可以使用Lookups.fixed(Object...
objectsToLookup) (注意:... 代表可變參數(shù)數(shù)量),也可以使用Lookups.singleton(Object
objectToLookup)返回一個只包含一個對象的Lookup.
2.
AbstractLookup和InstanceContent.
如果你希望你的Lookup可以動態(tài)的包含對象(數(shù)目可變),那么你可以使用AbstractLookup和InstanceContent相結(jié)合的方
式。注意AbstractLookup并不是抽象類。具體的用法這里有個很好的例子,Fox的Lookup講解:
在
這個例子中,有一個業(yè)務(wù)對象FoxObject,
有兩個頂層組件:Consumer頂層組件和Producer頂層組件。Producer頂層組件中包含兩個狀態(tài)按鈕
(ToggleButton):FoxButton和DonButton。Consumer頂層組件有一個List表單。
效果是:如果你
按下Producer頂層組件窗口中的其中一個Button,就會創(chuàng)建一個FoxObject對象,其名字根據(jù)按鈕的名字命名,例如按下
DonButton將創(chuàng)建一個名字為Don的FoxObject.
如果程序的焦點(diǎn)在Producer頂層組件上(藍(lán)色背景意味焦點(diǎn)在其上,灰色背景意味焦點(diǎn)不在其上)的話,Consumer頂層組件的List表單將出現(xiàn)
這個名字為Don的FoxObject,
當(dāng)你再次按下DonButton時,按鈕的狀態(tài)改變,名字為Don的FoxObject被刪除,List表單中相應(yīng)的對象也消失了。如果你將程序焦點(diǎn)換到
Consumer頂層組件的時候,List表單中的所有對象都消失了,重新將焦點(diǎn)換到Producer頂層組件上時,List表單中的對象重新出現(xiàn)了。
這是怎么實(shí)現(xiàn)的呢?我們看一下:
1. 我們在Producer頂層組件中維護(hù)一個InstanceContent對象,兩個FoxObject對象。在Producer頂層組件構(gòu)造的時候,我們初始化Lookup:
private void initLookup(){
// 創(chuàng)建InstanceContent,然后構(gòu)建一個AbstractLookup,然后將這個Lookup和Producer頂層組件關(guān)聯(lián)
m_InstancePool=new InstanceContent();
this.associateLookup(new AbstractLookup(m_InstancePool));
}
在ToggleButton狀態(tài)變化方法中,我們根據(jù)Button的狀態(tài),決定InstanceContent的內(nèi)容(名為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頂層組件實(shí)現(xiàn)LookupLisenter, 因?yàn)樗鶕?jù)FoxObject對象的狀態(tài)來顯式List表單。他維護(hù)一個JList表單以及一個Lookup.Result結(jié)果。
final class ConsumerTopComponent extends TopComponent implements LookupListener {...
當(dāng)Consumer頂層組件被打開和關(guān)閉時:
public void componentOpened() {
clearList();
// 初始化LookupQuery
initLookupQuery();
FoxService oService=(FoxService) Lookup.getDefault().lookup(FoxService.class);
if(oService !=null ){
oService.saySomething();
}
}
public void componentClosed() {
// 關(guān)閉LookupQuery
uninitLookupQuery();
clearList();
}
我
們看一下,在Consumer頂層組件窗口被打開時,我們根據(jù)FoxObject類制作一個模版,然后通過
Utilities.actionsGlobalContext()查找到所有FoxObject的實(shí)例,以Lookup.Result方式展現(xiàn)。找到這
些FoxObject實(shí)例后,對代表他們的Lookup.Result添加監(jiān)聽器,就是Consumer頂層組件自己。然后更新結(jié)果列表。
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;
}
}
實(shí)現(xiàn)Lookup監(jiān)聽器的方法:
public void resultChanged(LookupEvent ev){
refreshResultList();
}
一旦Lookup變化了,resultChanged方法就被調(diào)用,執(zhí)行refreshResultList方法。這個方法獲得Lookup.Result中的所有實(shí)例,如果有實(shí)例的話,重新根據(jù)這些實(shí)例繪制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();
}
}
整個演示就完成了。這里我們特別要注意幾點(diǎn):
1. FoxObject是業(yè)務(wù)對象,它僅僅關(guān)注自己的業(yè)務(wù)和信息,例如名字,ID等,對其他的事情一概不知。
2. Producer頂層組件需要和一個Lookup關(guān)聯(lián),這個Lookup是AbstractLookup和InstanceContent共同完成的,以便Lookup內(nèi)的對象數(shù)目可以動態(tài)改變(通過ToggleButton的狀態(tài)):
ProducerTopComponent --> AbstractLookup --> InstanceContent --> FoxObject Class
|
|__ Add/Remove --> Fox FoxObject <--> Button1
|
|__ Add/Remove --> Don FoxObject <-->
Button2
|
| 關(guān)鍵:Netbeans通過Utilities.actionsGlobalContext將
| Producer頂層組件和Consumer頂層組件解耦,相互通過Lookup溝通
ConsumerTopComponent --> Utilities.actionsGlobalContext().lookup(FoxObject.class)
|______ 監(jiān)聽 --> |__ Lookup.Result
|______ 更新 --> JList (根據(jù)Lookup.Result的變化)
3. 結(jié)構(gòu)圖
<-------------- FoxObject --------------->
|
| |
| <----- Lookup (Utilitis, AbstractLookup, InstanceConent) ------> |
| |
| |
ConsumerTopComponent XXX ProducerTopComponent
轉(zhuǎn)自http://alarnan.spaces.live.com/blog/cns!819cbc613de169ef!141.entry