Swing模型過濾器概述
Swing 體系結(jié)構(gòu)的重要?jiǎng)?chuàng)新之一在于采用了模型/視圖/控制器 (MVC) 原理,這樣就可將組件的不同角色分離開。當(dāng)一種體系結(jié)構(gòu)具備 MVC 分離特性時(shí),即可對(duì)組件的數(shù)據(jù)與狀態(tài)作不同的解釋。這允許程序員在組件及其模型之間插入過濾器對(duì)象。模型過濾可以在模型內(nèi)修改數(shù)據(jù)的表示,還也可以改變模型所封裝數(shù)據(jù)的外在數(shù)目和順序。
Swing模型過濾器的另外兩種重要特性是:
模型過濾操作不會(huì)改變底層的模型數(shù)據(jù)。這使得多個(gè)組件可以共享一組數(shù)據(jù),而且每個(gè)組件都可能以不同的方式解釋這組數(shù)據(jù)。
過濾器可以疊用,這樣就可以依次用幾個(gè)不同的過濾器對(duì)象來解釋模型數(shù)據(jù)。
已定義的代理
為了***限度地利用 Java 平臺(tái)對(duì)面向?qū)ο蟮闹С?,可以簡單地認(rèn)為組件由若干對(duì)象構(gòu)成。這些對(duì)象可以由一個(gè)通用術(shù)語 ― 代理 ― 來描述。代理是實(shí)現(xiàn)一個(gè)公共 Java 接口并與某個(gè)特定組件相關(guān)聯(lián)的對(duì)象。代理實(shí)現(xiàn)的接口定義代理在 MVC 體系結(jié)構(gòu)中充當(dāng)?shù)慕巧?/P>
對(duì)于剛剛接觸 Swing 的程序員而言,代理的概念似乎有些難以理解,但是,它們也是 AWT 組件的一種共同特征。例如,如果想更改 java.awt.Label 組件上的字體,只需創(chuàng)建或獲取 java.awt.Font 類的一個(gè)實(shí)例,并且調(diào)用 getFont() 使該實(shí)例與組件相關(guān)聯(lián)。Font 對(duì)象的內(nèi)部運(yùn)作細(xì)節(jié)可能很有趣,但是組件只要有 Font 類型對(duì)象的一個(gè)引用即可適當(dāng)?shù)仫@示自己。甚至像標(biāo)簽前景顏色這種簡單概念也是通過代理實(shí)現(xiàn)的;java.awt.Color 類提供一種適合作組件前景顏色的對(duì)象。作為一般規(guī)則,值為非基本數(shù)據(jù)類型的各種組件屬性都可看作是代理。
Swing 中的 MVC 實(shí)現(xiàn)就是這些概念的體現(xiàn)。對(duì)象不僅用于表示組件的屬性值,也用于表示組件行為的諸多方面。這種方案相當(dāng)靈活,足以支持 Swing 的可插接外觀 (PLAF) 功能的實(shí)現(xiàn),該功能使應(yīng)用程序既可模擬本地平臺(tái)的外觀,也可用一種與平臺(tái)無關(guān)的方案顯示組件。PLAF 既可使應(yīng)用程序看起來就像 Microsoft Windows、 Mac OS 和 X/Motif 等平臺(tái)的本地應(yīng)用程序一樣,也可使應(yīng)用程序具有一種中立的外觀,稱為 "Java" LAF 或 "Metal" LAF。
PLAF 功能與組件的外觀密切相關(guān)。本文主要討論這一體系結(jié)構(gòu)的模型部分,它與組件的外觀的無關(guān)。
作為一種模型(或類似一種模型)
每種支持?jǐn)?shù)據(jù)與狀態(tài)的 Swing 組件都有一種與之相關(guān)的模型接口。無論接口感興趣的是封裝于該模型的數(shù)據(jù)還是狀態(tài),它都會(huì)包含允許組件以編程方式查詢模型內(nèi)容的若干方法。
每個(gè)模型接口都提供兩類方法:一類方法提供對(duì)數(shù)據(jù)與狀態(tài)的訪問,而另一類方法允許組件或者其他對(duì)象注冊(cè)或取消注冊(cè)事件監(jiān)聽程序。監(jiān)聽程序的類型及其提供的事件對(duì)象都由這些方法定義。
Swing 模型接口可以有不同類型的類實(shí)現(xiàn)。在許多情況下,為模型提供的是一種抽象實(shí)現(xiàn);除了為了觸發(fā)模型所表示的各種事件方法而提供的 protected 方法之外,這通常是一種不完全的正則實(shí)現(xiàn)。所有模型都有一個(gè)缺省實(shí)現(xiàn),并且是一個(gè)具體類。
既好又簡單 ― ListModel 接口
在開始討論過濾之前,對(duì)典型的模型接口作一回顧不失為明智之舉。
ListModel 接口代表 JList 組件中的數(shù)據(jù)。這是三種集合模型中最簡單的一種。(另外兩種分別是 JTree 和 JTable。) ListModel 有兩個(gè)方法用于檢索列表中的元素個(gè)數(shù)以及各個(gè)元素,另外還有兩個(gè)方法用于維護(hù)感興趣的監(jiān)聽程序列表,以便監(jiān)聽列表模型的變化。
ListModel 的簡化源代碼
- package javax.swing;
- import javax.swing.event.ListDataListener;
- public interface ListModel
- {
- int getSize();
- Object getElementAt(int index);
- void addListDataListener(ListDataListener listener);
- void removeListDataListener(ListDataListener listener);
- }
在 ListModel 接口中, getSize() 與 getElementAt() 方法用于遍歷模型中的元素,而其他兩個(gè)方法用于建立與感興趣的監(jiān)聽程序之間的關(guān)聯(lián),以便監(jiān)聽模型的變化。
ListDataListener 接口支持三個(gè)方法,當(dāng)模型監(jiān)聽到其底層數(shù)據(jù)發(fā)生變化時(shí)就會(huì)調(diào)用這三個(gè)方法。這三個(gè)方法是 intervalAdded() 、 intervalRemoved() 和 contentsChanged() ,每個(gè)方法都接受單個(gè) ListDataEvent 作為參數(shù)。根據(jù)模型所發(fā)生變化的復(fù)雜程度之不同,模型實(shí)現(xiàn)可以使用其中的任一個(gè)方法來描述這些變化。通常, intervalAdded() 和 intervalRemoved() 用于描述變化的時(shí)間間隔;當(dāng)變化過于復(fù)雜,無法作為一個(gè)閉合間隔進(jìn)行描述時(shí),就會(huì)用到 contentsChanged() 。
為了理解模型過濾如何運(yùn)作,請(qǐng)記住這一點(diǎn):JList 組件只對(duì) ListModel API 的實(shí)現(xiàn)感興趣。該組件并不關(guān)心數(shù)據(jù)駐留何處以及數(shù)據(jù)是如何組織的。無論該模型是一個(gè)缺省類、抽象類的擴(kuò)展,還是 ListModel 接口的一種直接實(shí)現(xiàn),都不影響 JList 組件的行為
模型過濾的基本概念利用了 Swing 組件對(duì)模型類的底層實(shí)現(xiàn)缺乏了解這一事實(shí)。下圖說明了這種典型的
Swing模型過濾器是實(shí)現(xiàn)了模型接口、但并不真正包含數(shù)據(jù)的類。模型過濾器在組件與其模型之間進(jìn)行協(xié)調(diào)。模型過濾器可以重新解釋模型所提供的信息,并且可以更改所提供的數(shù)據(jù)元素個(gè)數(shù)、數(shù)據(jù)的順序以及數(shù)據(jù)本身。
在本例中,過濾器類是將一個(gè)現(xiàn)有模型類作為其數(shù)據(jù)源來實(shí)例化的。在模型過濾器的一般實(shí)現(xiàn)中,對(duì) API 方法的調(diào)用將委托給源模型。
由于此 API 是統(tǒng)一實(shí)現(xiàn)的,因此完全可以在組件與其模型之間“疊放”多個(gè)過濾器。注意,每個(gè)過濾層都要求每個(gè) API 調(diào)用穿過一個(gè)附加的間接層;如果過濾層過于復(fù)雜,則很可能造成性能瓶頸。
基本過濾器
下面顯示的抽象類是作用于 JList 組件之上的模型過濾器的基類。其唯一的構(gòu)造函數(shù)要求,模型過濾器的每個(gè)實(shí)例都要引用某個(gè)底層的模型數(shù)據(jù)。該數(shù)據(jù)既可以是另一個(gè)模型過濾器,也可以不是;在這兩種情況下,過濾器的行為是相同的。
Swing模型過濾器基類
- package com.ketherware.models;
- import javax.swing.*;
- public abstract class AbstractListModelFilter extends AbstractListModel
- {
- // 用來保存被過濾模型的引用
- protected ListModel delegate;
- // 構(gòu)造函數(shù) ― 接受單個(gè)參數(shù),其中包含被過濾模型的引用
- public AbstractListModelFilter(ListModel delegate)
- {
- this.delegate = delegate;
- }
- public ListModel getDelegate()
- {
- return this.delegate;
- }
- public int getSize()
- {
- // 委托給過濾器目標(biāo)
- return delegate.getSize();
- }
- public Object getElementAt(int index)
- {
- // 委托給過濾器目標(biāo)
- return delegate.getElementAt(index);
- }
- public void addListDataListener(ListDataListener listener)
- {
- // 委托給過濾器目標(biāo)
- delegate.addListDataListener(listener);
- }
- public void removeListDataListener(ListDataListener listener)
- {
- // 委托給過濾器目標(biāo)
- delegate.removeListDataListener(listener);
- }
- }
該類相當(dāng)于一種“空”過濾器,它不更改任何底層數(shù)據(jù)。因此,它沒有什么特別的意義。ListModel 過濾器類的實(shí)際實(shí)現(xiàn)將覆蓋該抽象類的方法,以便以不同的方式呈現(xiàn)底層數(shù)據(jù)。
您可以通過實(shí)現(xiàn)過濾器來改變底層數(shù)據(jù)事件的特性。為了使對(duì)模型過濾器的討論更易于理解,本文的示例都只針對(duì)不可變的數(shù)據(jù)模型,即不觸發(fā)任何模型事件的類。
缺省模型適合于要求不高的一般應(yīng)用。但是,您應(yīng)該了解這些缺省類都是為通用目的而設(shè)計(jì)的,因此,在對(duì)性能有嚴(yán)格要求的情況下,它們通常表現(xiàn)不佳。同樣,許多常用的模型都是作為可變模型來實(shí)現(xiàn)的,即,模型的數(shù)據(jù)可隨時(shí)間變化。當(dāng)已知數(shù)據(jù)為靜態(tài)數(shù)據(jù)時(shí),這些額外的行為可能是多余的。因此,您可能想另外構(gòu)建模型類,去掉由事件傳播所導(dǎo)致的額外開銷。
不可變模型
在許多情況下,根據(jù)模型的底層數(shù)據(jù)是否可變對(duì)模型進(jìn)行分類很有用。在數(shù)據(jù)不會(huì)變化的情況下,可以實(shí)現(xiàn)不可變的數(shù)據(jù)模型,這種模型不實(shí)現(xiàn)用于監(jiān)聽數(shù)據(jù)變化的監(jiān)聽程序。Swing 模型接口的缺省實(shí)現(xiàn)假定數(shù)據(jù)是可變的。
不可變模型的創(chuàng)建過程相當(dāng)簡單。您可以創(chuàng)建一個(gè)具體類,該類可提供模型接口,但為與事件相關(guān)的活動(dòng)所提供的所有方法都不執(zhí)行任何操作。根據(jù)模型要作為一般模型使用,還是作為專用模型使用,您既可將此不可變模型實(shí)現(xiàn)為一個(gè)抽象類,也可將其實(shí)現(xiàn)為一個(gè)具體類。
下面的示例是一個(gè)不可變的列表模型,我設(shè)計(jì)它時(shí)希望它非常通用,并且允許將支持 java.util.List 集合接口的任何對(duì)象用作數(shù)據(jù)源。返回的數(shù)據(jù)是一個(gè)籠統(tǒng)的 Object 類型;如何顯示對(duì)象留待 JList 及其相關(guān)繪制程序解釋。
【編輯推薦】