本文提出了一種界面設計中的架構模式界面組裝器模式它致力於分解界面將界面和組裝行為解耦將界面邏輯處理與領域邏輯處理解耦這樣我們在開發GUI胖客戶端界面應用時可以從眾多的界面控制管理中解脫出來而專注於我們的後台業務邏輯的開發通過該模式我們可以動態地組裝我們的界面我們甚至還可以在我們的界面中輕松地插入 transaction 事務或 session 會話管理
本文將通過分析設計一個架構的過程來講解該模式從一個簡單的設計模型開始一步步走向一個完整的架構借此也向大家展示一個架構設計的思維歷程另外本文給出了 Eclipse SWT(Standard Widget Toolkit) 的示例
問題引出
界面設計常常是模式產生的根源無論是架構模式還是設計模式比如 MVC 模式ObserverFacade 等也是整個軟件行業向前發展的動力遺憾的是即使在軟件技術發達的今天界面設計仍是軟件設計中的難以突破的瓶頸之一我們用過 Java swing 或 Eclipse SWT 作過項目的都知道要將界面進行分解是很困難的它不像我們的業務邏輯可以方便地按職責分解到不同的類中去實現因為各個業務邏輯之間耦合度很低但界面邏輯不一樣你不可能將一個文本框的讀取操作委任到另一個類中去而且各個界面元素之間相互依賴無法去除耦合一般的做法只能是在界面元素的事件觸發(比如按鈕點擊事件)時將輸入數據封裝成一個數據對象傳給後台的邏輯處理類來處理
Eclipse 的 Wizard 框架在界面分解上提供了一種很好的實踐它可以將按鈕區和其他界面區分離出來用類似 MVC 的方式實現了 Wizard 框架但這個實現並非沒有瑕疵一個缺點是 wizard 是一個 plugin這樣的話就減少了可重用性不能移植到 eclipse 以外的環境另一個缺點就是它引入了很大的復雜性而且在一些對界面元素的控制上喪失了一些精細控制的能力這可能是它過度地強調了自動化和用戶擴展的方便性的緣故比如用戶不能將自己的邏輯插入按鈕區的按鈕事件控制中而只能在自定義區的界面元素 Listener 中設定按鈕區狀態如果用戶自定義的界面元素很多就需要很多個 Listener 來組合判斷一個按鈕狀態(如是否進行下一步)這樣的話就很影響性能而且無端地多了一堆復雜的邏輯判斷也就是說本來只需在按鈕 Listener 事件中處理的邏輯現在要分散在各個界面元素的 Listener 中去處理這也正是設計上一個值得反復強調的普遍問題當你要保持架構或設計的完美性時必然會以喪失其他特性為代價世上永遠沒有完美的東西我們只關注適合我們的
我下面要提出的這個架構模式的靈感來自於我的一個真實項目一個用 RSA(Rational Software Architect)/Eclipse 建模的項目在 RSA 環境中讀寫模型都必須在一個特有的 context 下才能操作這就意味著我在界面的啟動之前必須封裝好輸入數據關閉之後返回輸出數據而不是直接處理數據必須對輸入/輸出數據對象進行封裝正如前面提到的這種情況界面設計中很普遍所以在模式命名時我用了組裝器assembler 這個詞有一層意思是輸入/輸出數據對象的組裝另一層意思就是界面部件(界面元素的集合)的組裝這裡的組裝還有更深層次的涵義就是指界面部件的可裝配性可以在運行時動態組裝而且這個模式可以用任何語言(JavaC++ 等)來實現在這裡我會從一個簡單的設計模型開始一步步走向一個完整的架構借此也向大家展示一個架構設計的思維歷程本文中給出了 Eclipse SWT(Standard Widget Toolkit) 的示例
界面分解
在 Eclipse SWT 中有幾個重要的界面部件一個是Shell界面的最外層容器類似 Java Swing 中的 Frame另一個就是 Composite界面元素的集合的容器類似 Java Swing 中的 Panel我們的界面分解將從 Composite 開始(Shell 本身是不需要分解的)我們可以在 Shell 中裝配上一個空的 Composite 然後我們的具體界面元素都定義在這個 Composite 裡這樣就把 Composite 邏輯從 Shell 中分離出來了因此我們現在有了 個類(目前我們用概念類來表示)
圖 把 Composite 邏輯從 Shell 中分離出來
Editor : 該類處理 Shell 的邏輯如顯示show關閉close它負責創建和銷毀 EditorComposite
EditorComposite: 該類處理 Composite 的界面邏輯如創建界面元素
有兩點值得注意第一Editor 負責 EditorComposite 的創建和銷毀也就是生命周期的管理那麼我們可以想到如果我們的界面需要 transaction事務或 session會話的管理那麼我們完全可以讓 Editor 來負責這項職責而不是分散在各個 EditorComposite 中怎麼擴展界面的事務功能可能會很復雜這已經超出本文的討論范圍我只是從架構的層面來分析可能有的可擴展性第二一個 Editor 可以包括多個 EditorComposite比如我們的屬性頁此時我們在Shell中定義的空的 Composite 將會是一個 TabFolder 還有一種情況就是我們可以根據某種邏輯來判斷我們需要裝配哪個 EditorComposite這就要求我們有一個裝配的行為
界面部件裝配
當我們的裝配邏輯很簡單時我們可以定義一個 assemble() 方法來負責裝配行為但是當我們的界面需要組裝一系列 EditorComposite 時就會牽涉到選擇邏輯選擇邏輯不一定很復雜但我們還是應該把這種行為從 Editor 中分離出來這樣 Editor 可以集中精力負責與用戶交互方面的職責而裝配行為被分配到一個新的類 EditorAssembler 中這樣做還有一個好處就是我們一旦有新的 EditorComposite 需要添加時我們只需要改變 EditorAssembler 的代碼而不用修改 Editor 的代碼這就把變化隔離出來對 Editor 的修改關閉對裝配行為的擴展開放這正是面向對象設計領域反復強調的基本原則開放封閉原則(OpenClose Principle)經過重構後的架構如下圖
圖 重構後的架構
EditorAssembler該類處理 EditorComposite 的創建還包括多個 EditorComposite 的選擇邏輯
這裡的選擇邏輯我們可以用 if/else 或 switch/case 來硬編碼如果邏輯不是很復雜而且今後的修改不會太頻繁的話用這種方法就足夠了當然可以考慮將多個 EditorComposite 的裝載信息專門用一個資源/信息類來存儲 target=_blank>存儲這在 EditorComposite 比較多的情況下很有效這樣每次添加 EditorComposite 就只需要改變這個資源類這是一個很有用的建模原則(為了簡化我們的核心模型我在這裡不將這個資源類表示出來)
如果進一步考慮到我們的組裝邏輯會比較復雜或會比較容易改變甚至在運行時動態改變我們就可以將眾多的 EditorComposite 和復雜的邏輯存儲在一個元數據文件中如 XML 或配置文件這樣有新的 EditorComposite 需要支持或修改裝配邏輯時不用修改 EditorAssembler 類只要修改元數據文件即可這樣就可以很動態的配置我們的界面這裡會有一個架構權衡的問題元數據由它的優點也有它的缺點其一必須編寫解析它的類復雜性增加了其二不需要編譯是它的優點也是它的缺點對 XML 或配置文件我們可以隨意修改只有在運行時發現異常才知道改錯了而且也可能被人蓄意破壞掉所以我們只在真的需要很頻繁地修改 EditorComposite 的配置或經常需要增加 EditorComposite 時才采用元數據方案在這裡我傾向於采用資源類方案
IO 數據裝配
模型設計進行到這裡我們似乎缺少了對數據流的建模在一個標准的界面程序中我們首先會有一組輸出數據比如按OK按鈕之後我們需要將界面元素上的輸入信息輸出到後台邏輯類來處理或直接調用好幾個邏輯類分別處理不同的界面元素輸入信息了我們一般習慣上可能直接將這個數據傳遞到邏輯類來處理這樣做三個缺點其一如果我們的數據讀寫處理要求必須在特定的 context 中才能進行這樣的話我們不能在界面中直接調用後台邏輯處理類了其實這種限制並不罕見在一些涉及底層(比如協議層)的開發時經常會碰到只能讀不能寫的情況其二UI 的可替代性差假如我們今後需要一種方案可以在運行時可以替換不同的 UI 但輸出的數據是一樣的也就是說後台邏輯處理完全一致那麼這種情況我們就需要每一個 UI 自己去調用後台邏輯類重復編碼而且可能由於程序員的失誤每一個 UI 用了一個邏輯類從而導致一個完全相同行為的類有了好幾個不一致實現版本這樣不僅嚴重違反了面向對象設計而且還可能產生難以預料的 bug難以維護其三UI 的可重用性差對於上面多個 UI 對應一種邏輯處理的例子由於 UI 依賴了後台邏輯類如果今後要修改邏輯類結構的話我們就需要修改每一個 UI如果我們還有一種需求是要支持一個 UI 在不同的環境下需要不同的後台邏輯類時我們可能要專門在一個 UI 中設置一個屬性來標識後台將要使用的邏輯類這會很復雜
解決上面幾個缺點只有一種方法就是將後台邏輯類與 UI 解耦如果我們把要處理的輸出數據打包成一個輸出數據對象從界面統一輸出再由 UI 的調用者決定調用哪一個後台邏輯類來處理數據而不是 UI 自己決定調用行為
還有一個輸入數據對象就很好理解了我們調用 UI 時可能某些界面元素需要的從環境中動態裝載數據比如一個下列列表還有一些我們上一次配置好的數據這次需要更新也需要將已有數據導入所以我們需要一個輸入數據對象這就得到下面的模型
圖 輸入數據對象
src=http://imgeducitycn/img_///jpg border= twffan=done>
InputDataObject該類封裝了輸入數據由 EditorComposite 負責解析這些數據
OutputDataObject該類封裝了輸出數據由 EditorComposite 負責產生這些數據
Editor 負責傳輸這兩個數據對象
從上面的模型我們可以看出 Editor 類其實相當於一個 Facade所有的界面與用戶的交互都由它負責集中調度管理Editor 會將裝配行為分配給 EditorAssembler 類來處理它還負責臨時存儲輸入輸出數據當然如果我們有類似 transaction 或 session 之類的處理會由 Editor 委派到別的相關類去處理應用 Facade 設計模式我們可以給 Editor 改個名字叫 EditorFacade這樣更能體現設計者的意圖千萬不要忽視類的命名設計是一門嚴肅的科學每一個細節我們都不能苟且對架構的設計更要嚴謹命名可以起到溝通的作用還能起到提醒的功能EditorFacade 提醒我們以後要給它添加新的行為是記住它是一個 Facade不能將不相干的職責分配進來
另外我發現添加了 InputDataObject 類後EditorComposite 就有兩個職責裝載界面元素初始化數據(一些需要從環境中動態獲得的輸入數據從 InputDataObject 對象中獲得)和顯示上一次編輯的數據(也從 InputDataObject 對象中獲得)我們定義兩個方法來分別處理loadDataInfo()裝載初始化數據;showPreInfo()顯示上一次編輯的數據當然一般來說這兩個方法是私有的private因為這是 EditorComposite 自身的內部邏輯但我們在這個架構中讓它成為公有的public是因為我們可以在 EditorAssembler 類中集中控制它的調用而且每一個 EditorComposite 都會有裝載初始化數據和顯示已有數據的行為那麼為什麼不抽象出來呢以便讓 EditorComposite 的開發提供者更清楚自己的職責雖然這麼做有點破壞 EditorComposite 的封裝性和其中方法的私密性但從架構的角度來講這種破壞是合適的值得的
再看看前面的 EditorAssembler 類它其實有兩個職責一個是創建 EditorComposite還有一個就是從幾個 EditorComposite 選擇出一個的判斷邏輯如果我們把這兩個不相干的職責解耦應用 Factory 設計模式就可以將創建 EditorComposite 的工作委任給一個 EditorCompositeFactory 的新類
經過以上幾項重構後得到以下概念類模型
圖 概念類模型
src=http://imgeducitycn/img_///jpg border= twffan=done>
經過上面的分析建模我們可以開始實現架構了從上面的概念模型我們可以很容易地抽象出相應的接口來首先我們看看 EditorFacade 類基於我們上面的討論不同的界面可能有不同的需求比如有的要支持 transaction事務那麼 EditorFacade 的實現就會不同所以我們有必要提取出一個接口來表示下面列出了這個接口 IEditorFacade
清單IEditorFacadejava
Code highlighting produced by Actipro CodeHighlighter (freeware)
>
public interface IEditorFacade {
public void show();
public IInputDataObject getInputData();
public void setInputData(IInputDataObject inputData);
public IOutputDataObject getOutputData();
public void setOutputData(IOutputDataObject outputData);
public boolean isFinishedOK();
public Composite getRootComposite();
public void setAssembler(IEditorAssembler assembler);
public void close(boolean status);
}
那麼 EditorFacade 類的部分代碼如下
清單EditorFacadejava
Code highlighting produced by Actipro CodeHighlighter (freeware)
>
public class EditorFacade implements IEditorFacade {
private Shell shell;
// validate if editor is closed with OK or Cancel
private boolean finishedOK;
// input data
private IInputDataObject inputData;
// output data
private IOutputDataObject outputData;
private Composite composite;
private IEditorAssembler assembler;
private void createSShell() {
shell = new Shell();
shell
setLayout(new GridLayout());
createComponent();
}
private void createComponent() {
composite = new Composite(shell
SWT
NONE);
………
assembler
create(this);
}
public void show() {
this
shell
open();
assembler
showPreInfo();
}
public EditorFacade(IEditorAssembler assembler
IInputDataObject inputData) {
this
assembler = assembler;
this
inputData = inputData;
this
createSShell();
}
public Composite getRootComposite() {
return composite;
}
public void close(boolean status) {
finishedOK = status;
this
shell
close();
}
}
下一步我們將兩個 IO 數據類定義出來很顯然不同的界面會有不同的輸入輸出數據在這裡我們只能定義出兩個抽象的接口 IInputDataObject 和 IOutputDataObject它們繼承了序列化 javaioSerializable 接口裡面並無其它內容這裡注意一點空的接口並非無意義它可以起到標識的作用另外它隱藏了具體實現在傳遞數據時傳遞者不用知道具體數據內容這樣傳遞者類具有更好的重用性而且具體數據類也不用暴露給不該知道它的類傳遞者類這正是另一個面向對象的基本原則迪米特法則(LoD)不要和陌生人說話下面給出 IInputDataObject 的清單
清單IInputDataObjectjava
Code highlighting produced by Actipro CodeHighlighter (freeware)
>
public interface IInputDataObject extends Serializable {
}
接下來我們看看 EditorAssembler 類的實現根據前面的討論它封裝了界面的裝配邏輯一定會被修改的那麼我們就需要一個接口 IEditorAssembler 來規范它的行為在這裡我還給出了一個抽象類 AbstractEditorAssembler實現了裝載單個 EditorComposite 的方法另外我還給出了一個具體的 EditorAssembler 類這是一個每次只裝載一個 EditorComposite 的例子代碼清單如下
清單IEditorAssemblerjava
Code highlighting produced by Actipro CodeHighlighter (freeware)
>
public interface IEditorAssembler {
/**
* create editor body and init
* @param editor
*/
public void create(IEditorFacade editor);
/**
* create editor composite
* @param editor
* @param compositeClassID
* :composite class name
e
g
test
view
TestComposite
* @return
*/
public IEditorComposite createComposite(IEditorFacade editor
String compositeClassID);
/**
* show exist info in UI for update
*/
public void showPreInfo();
}
public interface IEditorAssembler {
/**
* create editor body and init
* @param editor
*/
public void create(IEditorFacade editor);
/**
* create editor composite
* @param editor
* @param compositeClassID
* :composite class name
e
g
test
view
TestComposite
* @return
*/
public IEditorComposite createComposite(IEditorFacade editor
String compositeClassID);
/**
* show exist info in UI for update
*/
public void showPreInfo();
}
清單AbstractEditorAssemblerjava
Code highlighting produced by Actipro CodeHighlighter (freeware)
>
public abstract class AbstractEditorAssembler implements IEditorAssembler {
public IEditorComposite createComposite(IEditorFacade editor
String compositeClassID) {
IEditorComposite body;
body = EditorCompositeFactory
createComposite(compositeClassID
editor);
body
create(editor
getRootComposite());
body
setEditor(editor);
return body;
}
…………………………………
}
相關rss
>
相關keyword
>
清單StandaloneEditorAssemblerjava
Code highlighting produced by Actipro CodeHighlighter (freeware)
>
public class StandaloneEditorAssembler extends AbstractEditorAssembler {
private String compositeClassID;
private IEditorComposite bodyComposite;
/**
*
* @param compositeClassID
* :composite class qulified name
e
g
com
ibm
XXComposite;
*/
public StandaloneEditorAssembler(String compositeClassID) {
positeClassID = compositeClassID;
}
public void create(IEditorFacade editor) {
bodyComposite = createComposite(editor
compositeClassID);
if (bodyComposite != null)
bodyComposite
loadDataInfo();
}
public void showPreInfo() {
bodyComposite
showPreInfo();
}
}
接下來是 EditorCompositeFactory 的實現這個類的實現比較簡單只是根據類名產生類
清單EditorCompositeFactoryjava
Code highlighting produced by Actipro CodeHighlighter (freeware)
>
public class EditorCompositeFactory {
/**
* create IEditorComposite
* @param clsName
* @param editor
* @return
*/
public static IEditorComposite createComposite(String clsName
IEditorFacade editor) {
IEditorComposite composite = null;
try {
Class cls = Class
forName(clsName);
if (cls != null)
composite = (IEditorComposite) cls
newInstance();
} catch (Exception e) {
e
printStackTrace();
}
if (composite != null) {
composite
setEditor(editor);
}
return composite;
}
}
最後就是 EditorComposite 的實現了很顯然每個界面的 EditorComposite 都不一樣所以我們在這裡只定義了一個接口來規范一下行為具體的 EditorComposite 實現我會在代碼附件中的測試包中給出
清單IEditorCompositejava
Code highlighting produced by Actipro CodeHighlighter (freeware)
>
public interface IEditorComposite {
/** set up composite UI */
public void create(Composite parent);
/** set the current editor for shell close and data set */
public void setEditor(IEditorFacade editor);
/** show previous data information in UI */
public void showPreInfo();
public void loadDataInfo();
}
下面我們編寫一些測試代碼來測試它這個測試應用是要編寫一個電話簿為了簡單起見我只定義了一個 EditorCompositePhoneBookComposite 在編寫組裝邏輯時也只是示例性地改變了一下界面的標題和尺寸(詳細代碼見代碼下載)
清單PhoneBookEditorAssemblerjava
Code highlighting produced by Actipro CodeHighlighter (freeware)
>
public void create(IEditorFacade editor) {
if (compositeType ==
) {
//it is a phone book
bodyComposite = createComposite(editor
test
PhoneBookComposite
);
|
|
|
XML error: The previous line is longer than the max of
characters
|
editor
getShell()
setText(
Phone Book
);
editor
getShell()
setSize(
);
editor
getShell()
redraw();
if (bodyComposite != null)
bodyComposite
loadDataInfo();
} else if (compositeType ==
) {
//it is a memo book
bodyComposite = createComposite(editor
test
PhoneBookComposite
);
|
|
|
XML error: The previous line is longer than the max of
characters
|
editor
getShell()
setText(
Memo Book
);
editor
getShell()
setSize(
);
editor
getShell()
redraw();
if (bodyComposite != null)
bodyComposite
loadDataInfo();
}
}
清單Mainjava
Code highlighting produced by Actipro CodeHighlighter (freeware)
>
public static void main(String[] args) {
//定義PhoneBook EditorAssembler
IEditorAssembler assembler = new PhoneBookEditorAssembler(
);
//定義PhoneBook 輸入數據
IInputDataObject inputData = new PhoneBookInputDO(
LYL
);
//定義PhoneBook editor
EditorFacade editor = new EditorFacade(assembler
inputData);
editor
show();
if (editor
isFinishedOK()) {
//取出PhoneBook 輸出數據
if (editor
getOutputData() instanceof PhoneBookOutputDO) {
PhoneBookOutputDO outputData = (PhoneBookOutputDO) editor
getOutputData();
String name = outputData
getName();
String phone = outputData
getPhone();
System
out
println(
name:
+ name +
; phone:
+ phone);
}
}
}
接下來我們可以看一下架構的實現模型注意我在畫下面的 UML 圖時采用了分層的方式所有的接口都會在上面一層實現在下面一層這種分層畫 UML 圖的方法有助於我們理清架構的思路也便於與開發組的其他成員溝通
圖 架構的實現模型
alt= src=http://imgeducitycn/img_///jpg width= border= twffan=done>
至此我們完成了界面組裝器的核心架構的實現注意這只是一種實現並不是界面組裝模式的全部作為一種模式它必須有更廣的外延下面我們將要探討它的模式本質
這個模式是一種架構模式模式的定義有三個要素問題環境解決方案這在前面我們已經詳細地論述過了在這裡我們討論一下其他的參量每個模式都有它自己獨特的價值觀那麼界面組裝器模式給我們提供了什麼樣的價值觀呢?
首先它的精髓在於這種分解界面將界面和組裝行為解耦的設計思想這在擁有多個界面的應用中很有益處當界面多的時候如果沒有一個比較集中的調度控制方式來對這些界面進行管理就會形成界面行為無法規范風格各異更難以作 transaction 事務或 session 會話控制這在小型應用開發中也許不很明顯但在一個大中型應用中對分散的不規范的界面行為進行控制將會是一場惡夢到最後可能整個開發組都沉浸於 bug 的修復和界面修改中而無暇顧及領域邏輯代碼的編寫而通過將界面和組裝行為解耦就可以讓開發人員集中精力於界面邏輯和領域邏輯的開發而不用每一個界面都去編寫管理界面的代碼其實這也是模式化的一個優點模式可以優化我們的架構可以規范開發行為因此也會節省開發成本
其二它將界面邏輯處理與領域邏輯處理(也就是數據邏輯處理)解耦我們將數據輸入輸出從界面模型中抽取出來沒有與界面耦合在一起這就獲得巨大的好處第一我們可以在界面之外來處理數據在我們的領域類中處理這些數據也就是說界面只是提供了一個定義數據的載體而這些數據是被領域邏輯類使用的而我們開發的主要精力也應該放在處理業務邏輯的領域類上第二現在我們將界面和領域類解耦這樣我們的界面和領域類都可以獨立地變化相互之間沒有任何依賴這就很方便於我們開發人員的分工編寫界面的開發組不用依賴於編寫後台邏輯類的開發組第三在做單元測試unit test 時開發後台邏輯類的人員可以單獨測試領域類而開發界面的人員也可以單獨測試界面邏輯第四當我們有多套界面機制時我們的後台邏輯類可以很方便地接插上去比如我們要支持 GUI(SWT/Java Swing)和 Web 方式那麼我們的領域類和數據類無需任何更改就可以方便的切換第五我們還能獲得好處就是數據類的可重用如果我們沒有輸入輸出數據類的封裝行為那可能我們會將各條數據散落在界面類中直接處理這樣當你要換一種界面機制時就必須重寫這部分邏輯無法重用
作為一種模式它會有很多的變體也就是說它不拘泥於我們給出的這種外在實現方式它還有其它的實現例子中我們只是組裝一個 EditorComposite我們當然可以一次組裝幾個 EditorComposite比如一個復雜的界面會有好幾個 EditorComposite 組成或者像屬性頁並列著有好幾個 EditorComposite我們只需要自己實現一個組裝器類 Assembler 就可以又或者我們可以在運行界面時動態地在幾個界面之間切換界面這可能會復雜一些也受限於平台或語言的技術實現但也並非不可實現
對於該模式的適用性我想它主要適用於那些每次裝載一個 EditorComposite 或屬性頁的情況至於是否可以作為 Wizard 向導界面的實現架構還需進一步探索不過從這個模式的概念層次上來看它的關鍵的價值觀是完全可以用於實現 Wizard 向導界面的只不過具體實現時可能會對現在的架構變動較大另外這個模式主要適用於 GUI 客戶端界面對於 Web 形式的界面已經有別的模式可以考慮
我們還可以討論一下界面組裝器模式與別的模式之間的關系在界面架構界我們已經有了大名鼎鼎的 MVC 模式為什麼還需要界面組裝器模式呢?雖然 MVC 模式解決的也是界面與領域邏輯處理的解耦但它的出發點主要是針對一個業務邏輯處理後會有好幾個界面同時需要更新顯示也就是說它的貢獻在於他的及時傳播數據變更的能力這和我們的模式是不一致的我們主要解決界面的分解組裝和數據剝離的問題當然他們在結構上有些相似之處我們的 EditorFacade 有點像 MVC 中的控制器
結束語
本文所講述的界面組裝器模式為我們提供了將界面和組裝行為解耦將界面邏輯處理與領域邏輯處理解耦的價值觀在 GUI 胖客戶端型界面中可以大量應用筆者已經在幾個大型項目中應用了它所以它的可行性是經過實踐檢驗的當然任何模式不管是設計模式還是架構模式都有它的適用性只有合適的沒有絕對的優劣我們是否應用模式是在於模式為我們提供的價值觀是否和我們的需求期望符合而不是因為別的原因
From:http://tw.wingwit.com/Article/program/Java/gj/201311/27337.html