Swing 模型過濾

 
本文討論模型過濾技術。您可將這一技術用於 Swing 組件集,這樣即可在不改變底層數據的條件下提供模型數據的不同視圖。過濾器可以改變數據元素的外在內容,將數據排除在視圖之外、將外部元素包含進數據集 中、或者以不同的順序呈現元素。過濾器既可應用於數據模型,也可應用於狀態模型。您還可以疊用過濾器,以將它們的效果組合起來。

 

簡介

模型過濾是這樣一種技術,它在 Swing 組件體系結構中提供附加的功能與靈活性。

Swing 體系結構的重要創新之一在於採用了模型/視圖/控制器 (MVC) 原理,這樣就可將組件的不同角色分離開。當一種體系結構具備 MVC 分離特性時,即可對組件的數據與狀態作不同的解釋。這允許程序員在組件及其模型之間插入過濾器對象。模型過濾可以在模型內修改數據的表示,還也可以改變模型所封裝數據的外在數目和順序。

模型過濾器的另外兩種重要特性是:

  • 模型過濾操作不會改變底層的模型數據。這使得多個組件可以共享一組數據,而且每個組件都可能以不同的方式解釋這組數據。
  • 過濾器可以疊用,這樣就可以依次用幾個不同的過濾器對象來解釋模型數據。

已定義的代理
爲了最大限度地利用 Java 平臺對面向對象的支持,可以簡單地認爲組件由若干對象構成。這些對象可以由一個通用術語 ― 代理 ― 來描述。代理是實現一個公共 Java 接口並與某個特定組件相關聯的對象。代理實現的接口定義代理在 MVC 體系結構中充當的角色。

對於剛剛接觸 Swing 的程序員而言,代理的概念似乎有些難以理解,但是,它們也是 AWT 組件的一種共同特徵。例如,如果想更改 java.awt.Label 組件上的字體,只需創建或獲取 java.awt.Font 類的一個實例,並且調用 getFont() 使該實例與組件相關聯。Font 對象的內部運作細節可能很有趣,但是組件只要有 Font 類型對象的一個引用即可適當地顯示自己。甚至像標籤前景顏色這種簡單概念也是通過代理實現的;java.awt.Color 類提供一種適合作組件前景顏色的對象。作爲一般規則,值爲非基本數據類型的各種組件屬性都可看作是代理。

Swing 中的 MVC 實現就是這些概念的體現。對象不僅用於表示組件的屬性值,也用於表示組件行爲的諸多方面。這種方案相當靈活,足以支持 Swing 的可插接外觀 (PLAF) 功能的實現,該功能使應用程序既可模擬本地平臺的外觀,也可用一種與平臺無關的方案顯示組件。PLAF 既可使應用程序看起來就像 Microsoft Windows、 Mac OS 和 X/Motif 等平臺的本地應用程序一樣,也可使應用程序具有一種中立的外觀,稱爲 "Java" LAF 或 "Metal" LAF。

PLAF 功能與組件的外觀密切相關。本文主要討論這一體系結構的模型部分,它與組件的外觀的無關。

 

作爲一種模型(或類似一種模型)

每種支持數據與狀態的 Swing 組件都有一種與之相關的模型接口。無論接口感興趣的是封裝於該模型的數據還是狀態,它都會包含允許組件以編程方式查詢模型內容的若干方法。

每個模型接口都提供兩類方法:一類方法提供對數據與狀態的訪問,而另一類方法允許組件或者其他對象註冊或取消註冊事件監聽程序。監聽程序的類型及其提供的事件對象都由這些方法定義。

Swing 模型接口可以有不同類型的類實現。在許多情況下,爲模型提供的是一種抽象實現;除了爲了觸發模型所表示的各種事件方法而提供的 protected 方法之外,這通常是一種不完全的正則實現。所有模型都有一個缺省實現,並且是一個具體類。

既好又簡單 ― ListModel 接口
在開始討論過濾之前,對典型的模型接口作一回顧不失爲明智之舉。

ListModel 接口代表 JList 組件中的數據。這是三種集合模型中最簡單的一種。(另外兩種分別是 JTree 和 JTable。) ListModel 有兩個方法用於檢索列表中的元素個數以及各個元素,另外還有兩個方法用於維護感興趣的監聽程序列表,以便監聽列表模型的變化。

 

ListModel 的簡化源代碼

在 ListModel 接口中, getSize()getElementAt() 方法用於遍歷模型中的元素,而其他兩個方法用於建立與感興趣的監聽程序之間的關聯,以便監聽模型的變化。

ListDataListener 接口支持三個方法,當模型監聽到其底層數據發生變化時就會調用這三個方法。這三個方法是 intervalAdded()intervalRemoved()contentsChanged() ,每個方法都接受單個 ListDataEvent 作爲參數。根據模型所發生變化的複雜程度之不同,模型實現可以使用其中的任一個方法來描述這些變化。通常, intervalAdded()intervalRemoved() 用於描述變化的時間間隔;當變化過於複雜,無法作爲一個閉合間隔進行描述時,就會用到 contentsChanged()

爲了理解模型過濾如何運作,請記住這一點:JList 組件只對 ListModel API 的實現感興趣。該組件並不關心數據駐留何處以及數據是如何組織的。無論該模型是一個缺省類、抽象類的擴展,還是 ListModel 接口的一種直接實現,都不影響 JList 組件的行爲。

 

過濾的基本知識

模型過濾的基本概念利用了 Swing 組件對模型類的底層實現缺乏瞭解這一事實。下圖說明了這種典型的關係:

 

模型過濾器是實現了模型接口、但並不真正包含數據的類。模型過濾器在組件與其模型之間進行協調。模型過濾器可以重新解釋模型所提供的信息,並且可以更改所提供的數據元素個數、數據的順序以及數據本身。

 

在本例中,過濾器類是將一個現有模型類作爲其數據源來實例化的。在模型過濾器的一般實現中,對 API 方法的調用將委託給源模型。

由於此 API 是統一實現的,因此完全可以在組件與其模型之間“疊放”多個過濾器。注意,每個過濾層都要求每個 API 調用穿過一個附加的間接層;如果過濾層過於複雜,則很可能造成性能瓶頸。

基本過濾器
下面顯示的抽象類是作用於 JList 組件之上的模型過濾器的基類。其唯一的構造函數要求,模型過濾器的每個實例都要引用某個底層的模型數據。該數據既可以是另一個模型過濾器,也可以不是;在這兩種情況下,過濾器的行爲是相同的。

 

模型過濾器基類

 

 

該類相當於一種“空”過濾器,它不更改任何底層數據。因此,它沒有什麼特別的意義。ListModel 過濾器類的實際實現將覆蓋該抽象類的方法,以便以不同的方式呈現底層數據。

您可以通過實現過濾器來改變底層數據事件的特性。爲了使對模型過濾器的討論更易於理解,本文的示例都只針對不可變的數據模型,即不觸發任何模型事件的類。

缺省模型適合於要求不高的一般應用。但是,您應該瞭解這些缺省類都是爲通用目的而設計的,因此,在對性能有嚴格要求的情況下,它們通常表現不佳。同樣,許 多常用的模型都是作爲可變模型來實現的,即,模型的數據可隨時間變化。當已知數據爲靜態數據時,這些額外的行爲可能是多餘的。因此,您可能想另外構建模型 類,去掉由事件傳播所導致的額外開銷。

不可變模型
在許多情況下,根據模型的底層數據是否可變對模型進行分類很有用。在數據不會變化的情況下,可以實現不可變的數據模型,這種模型不實現用於監聽數據變化的監聽程序。Swing 模型接口的缺省實現假定數據是可變的。

不可變模型的創建過程相當簡單。您可以創建一個具體類,該類可提供模型接口,但爲與事件相關的活動所提供的所有方法都不執行任何操作。根據模型要作爲一般模型使用,還是作爲專用模型使用,您既可將此不可變模型實現爲一個抽象類,也可將其實現爲一個具體類。

下面的示例是一個不可變的列表模型,我設計它時希望它非常通用,並且允許將支持 java.util.List 集合接口的任何對象用作數據源。返回的數據是一個籠統的 Object 類型;如何顯示對象留待 JList 及其相關繪製程序解釋。


不可變模型的示例

 

下面將討論四種類型的過濾器:替換、排序、排除和包含。

 

替換過濾器

替換過濾的目的在於,重新解釋模型數據,並且通過改變返回的對象元素來表示它。這種類型的過濾器不改變數據元素的順序,它既不刪除數據,也不創建額外的數據。

下面是一個替換過濾器的示例,它爲底層模型中的每個數據項添加一個數字索引。唯一的變化是覆蓋了單個方法。


替換過濾器的示例

 

在許多情況下,在繪製程序中引入補充的特性可能更合適,比如填加一個行索引。您可以提供一個過濾器,它通過與繪製程序交互來提供額外的圖形表示。使用過濾器代替繪製程序的優點在於,可用一個組件顯示經過索引的數據,而無須與繪製程序相關聯。

替換過濾器通常不覆蓋 getSize() ,而且不改變所返回元素的順序。

 

排序過濾器

排序過濾器代表了另一層面的複雜性。它們不改變所表示元素的個數,在這一點上與替換過濾器類似。排序過濾器改變模型中經過索引的元素順序。其基本技術在於,創建模型元素的一種替代索引,用於代替實際的順序。

排序過濾器的一種常見類型是分類過濾器,它基於某個明確的排序順序重新索引數據。下面的示例按字母順序排列任一個 ListModel 實現的內容。


排序過濾器的示例

 

可以將一種排序過濾器用於 JTable 組件,以便對錶數據執行面向列的排序;這種過濾器的代碼類似於上面的示例。通過修改 JTable 的表頭和表的模型組件,該過濾器可以得到進一步的增強。

請注意,上面的示例只對不可變列表模型有效。如果數據在動態變化,爲了修改在事件被觸發時由 ListDataEvent 對象傳遞的索引,必須提供一些附加支持。這將顯著增加過濾器的複雜性,我將它的實現作爲一個練習留給讀者。

排序過濾器的主要特徵在於,他們不增加或者減少模型的可見元素個數,因此, getSize() 將委託給被過濾的模型。他們通常將不改變數據元素,而只是按照某種替代順序解釋數據的索引。

 

排除過濾器

最後兩種類型的過濾器非常相似,但是,擁有完全不同的目的。排除過濾器與包含過濾器都允許對模型的數據元素進行限制或者補充額外的元素。

排除過濾器使模型中的某些元素看似不存在。在只有單一數據源可用、並且實現方案只要求顯示數據的一個子集的情況下,這些過濾器相當有效。

關於典型的排除過濾器的示例,請參考 TerritoryListModelFilter.java。該示例給出了一個銷售區域列表,其中每個區域都與一個銷售人員相關聯。當選定一個銷售人員的姓名時,過濾器只顯示與該銷售人員相關聯的那些區域。

這個示例的優點非常明顯:如果不進行過濾,則每次選定一個不同的銷售人員都需要重新加載數據模型,或者在高速緩存中保存大量的模型實例。過濾器甚至允許兩個不同的組件用兩種不同的解釋方案查看同一個基本模型。

 

包含過濾器

包含過濾器儘管不像排除過濾器那樣廣泛適用,但它們可用來向模型中添加信息。由於這種類型的過濾器可用於進行總計或者小計,這些過濾器的最佳用途是報表應用程序。

執行總計操作的過濾器創建一個虛擬元素,並將其顯示在列表模型的尾部。爲了實現這一功能,過濾器將模型大小的值加 1,並將對除最後一個元素之外的所有元素的請求發送至代理。 SalesTotalListModelFilter.java 中的示例假定列表數據是不可變的;過濾器將列表數據事件忽略。這裏再一次用到前一個示例中的 TerritoryListModel。

 

 

小結

這些示例已經顯示了模型過濾的某些應用。過濾是一種應用相當廣泛的概念,遠遠不止本文這些相對比較簡單的應用。當您開始實現過濾器時,請記住下列幾點:

  • 過濾可以向不同組件提供不同的視圖,並且可以減少應用程序必須支持的完整模型實例的個數。
  • 過濾可以應用於 Swing 支持的其他模型,包括選擇模型。
  • 您可以爲處理可變模型或者動態模型構造非常複雜的過濾方案。爲了實現這一點,可以用一個過濾器來處理由該代理模型傳遞的事件。
  • 您可以無限地嵌套(或疊用)過濾器,但是,當每次修改或者查詢模型時,每個過濾層都會增加一些額外的處理負擔。

<script src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type="text/javascript"></script>
轉貼請註明出處:http://blog.csdn.net/froole

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章