### Separate Domain from Presentation(將領域和表述/顯示分離)
(譯注:本節保留domain-,presentation-logic,UI,class,object等英文詞)
某些GUI class 之中包含了domain logic(領域邏輯)。
將domain logic(領域邏輯)分離出來,為它們建立獨立的domain class。

**動機(Motivation)**
提到面向對象,就不能不提model-view-controller (MVC,模型-視圖-控制器) 模式。在Smalltalk-80環境中,人們以此模式維護GUI(圖形用戶界面)和domain object (領域對象)間的關系。
MVC模式的最核心價值在于:它將用戶界面代碼(即所謂view,視圖;亦即現今常說的presentation,表述)和領域邏輯(即所謂model,模型)分離了。presentation class 只含用以處理用戶界面的邏輯;domain class不含任何與程序外觀相關的代碼,只含業務邏輯(business logic)相關代碼。將程序中這兩塊復雜的部分加以分離,程序未來的修改將變得更加容易,同時也使同一業務邏輯(business logic)的多種表述(顯示)方式成為可能。那些熟稔面向對象技術的程序員會毫不猶豫地在他們的程序中進行這種分離,并且這種作法也的確證實了它自身的價值。
但是,大多數人并沒有在設計中采用這種方式來處理GUI。大多數帶有client-server GUIs 的環境都釆用雙層(two-tier)邏輯設計:數據保存在數據庫中,業務邏輯(business logic)放在presentation class 中。這樣的環境往往迫使你也傾向這種風格的設計,使你很難把業務邏輯放在其他地方。
Java 是一個真正意義上的面向對象環境,因此你可以創建內含業務邏輯的非視覺性領域對象(nonvisual domain objects )。但你卻還是會經常遇到上述雙層風格寫就的程序。
**作法(Mechanics)**
- 為每個窗口(window)建立一個domain class。
- 如果窗口內有一張表格(grid),新建一個class 來表示其中的行(row),再以窗口所對應之domain class 中的一個群集(collection)來容納所有的row domain objects。
- 檢查窗口中的數據。如果數據只被用于UI,就把它留著;如果數據被domain logic使用,而且不顯示于窗口上,我們就以Mocve Field 將它搬移到domain class 中;如果數據同時被UI 和domain logic 使用,就對它實施Duplicate Observed Data,使它同時存在于兩處,并保持兩處之間的同步。
- 檢査presentation class 中的邏輯。實施 Extract Method 將presentation logic 從domain logic 中分開。一旦隔離了domain logic。再運用 Move Method 將它移到domain class。
- 以上步驟完成后,你就擁有了兩組彼此分離的classes:presentation classes 用以處理GUI,domain logic 內含所有業務邏輯(business logic)。此時的domain classes 組織可能還不夠嚴謹,更進一步的重構將解決這些問題。
**范例:(Example)**
下面是一個商品訂購程序。其GUI 如圖12.7所示,其presentation class 與圖12.8 所示的關系式數據庫(relational database)互動。

圖12.7 啟動程序的用戶界面

圖12.8 訂單程序所用的數據庫
- 所有classes 都是《SQL table》,粗體字表示主鍵(primary key),《FK》表示外鍵(foreign keys)
所有行為(包括GUI 和定單處理)都由OrderWindow class 處理。
首先建立一個Order class 表示「定單」。然后把Order 和OrderWindow 聯系起來, 如圖12.9。由于窗口中有一個用以顯示定單的表格(grid),所以我們還得建立一個OrderLine,用以表示表格中的每一行(rows)。

圖12.9 OrderWindow class 和Order class
我們將從窗口這邊而不是從數據庫那邊開始重構。當然,一開始就把domain model 建立在數據庫基礎上,也是一種合理策略,但我們最大的風險源于presentation logic 和domain logic之間的混淆,因此我們首先基于窗口將這些分離出來,然后再考慮對其他地方進行重構。
面對這一類程序,在窗口中尋找內嵌的SQL (結構化查詢語言)語句,會對你有所幫助,因為SQL 語句獲取的數據一定是domain data 。
最容易處理的domain data 就是那些不直接顯示于GUI 者。本例數據庫的Customer table 中有一個Codes 值域,它并不直接顯示于GUI,而是被轉換為一個更容易被人理解的短語之后再顯示。程序中以簡單型別(而非AWT組件)如String 保存這個值域值。我們可以安全地使用Move Field 將這個值域移到domain class。
對于其他值域,我們就沒有這么幸運了,因為它們內含AWT 組件,既顯示于窗口, 也被domain object 使用。面對這些值域,我們需要使用Duplicate Observed Data,把一個domain field 放進Order class,又把一個相應的AWT field 放進OrderWindow class。
這是一個緩慢的過程,但最終我們還是可以把所有domain logic field 都搬到domain class。進行這一步驟時,你可以試著把所有SQL calls 都移到domain class,這樣你就是同時移動了database logic 和domain data。最后,你可以在OrderWindow 中移除import java.sql 之類的語句,這就表示我們的重構告一段落了。在此階段中 你可能需要大量運用 Extract Method 和 Move Method。
現在,我們擁有的三個classes,如圖12.10所示,它們離「組織良好」還有很大的距離。不過這個模型的確已經很好地分離了presentation logic 和domain logic (business logic)。本項重構的進行過程中,你必須時刻留心你的風險來自何方。 如果「presentation logic 和domain logic 混淆」是最大風險,那么就先把它們完全分開,然后才做其他工作;如果其他方面的事情(例如產品定價策略〉更重要,那么就先把那一部分的logic 從窗口提煉出來,并圍繞著這個高風險部分進行重構,為它建立合適的結構。反正domain logic 早晚都必須從窗口移出,如果你在處理高風險部分的重構時會遺留某些logic 于窗口之中,沒關系,就放手去做吧。

圖12.10 將數據安置(分散)于domain classes 中
- 譯序 by 侯捷
- 譯序 by 熊節
- 序言
- 前言
- 章節一 重構,第一個案例
- 起點
- 重構的第一步
- 分解并重組statement()
- 運用多態(Polymorphism)取代與價格相關的條件邏輯
- 結語
- 章節二 重構原則
- 何謂重構
- 為何重構
- 「重構」助你找到臭蟲(bugs)
- 何時重構
- 怎么對經理說?
- 重構的難題
- 重構與設計
- 重構與性能(Performance)
- 重構起源何處?
- 章節三 代碼的壞味道
- Duplicated Code(重復的代碼)
- Long Method(過長函數)
- Large Class(過大類)
- Long Parameter List(過長參數列)
- Divergent Change(發散式變化)
- Shotgun Surgery(散彈式修改)
- Feature Envy(依戀情結)
- Data Clumps(數據泥團)
- Primitive Obsession(基本型別偏執)
- Switch Statements(switch驚悚現身)
- Parallel Inheritance Hierarchies(平行繼承體系)
- Lazy Class(冗贅類)
- Speculative Generality(夸夸其談未來性)
- Temporary Field(令人迷惑的暫時值域)
- Message Chains(過度耦合的消息鏈)
- Middle Man(中間轉手人)
- Inappropriate Intimacy(狎昵關系)
- Alternative Classes with Different Interfaces(異曲同工的類)
- Incomplete Library Class(不完美的程序庫類)
- Data Class(純稚的數據類)
- Refused Bequest(被拒絕的遺贈)
- Comments(過多的注釋)
- 章節四 構筑測試體系
- 自我測試代碼的價值
- JUnit測試框架
- 添加更多測試
- 章節五 重構名錄
- 重構的記錄格式
- 尋找引用點
- 這些重構準則有多成熟
- 章節六 重新組織你的函數
- Extract Method(提煉函數)
- Inline Method(將函數內聯化)
- Inline Temp(將臨時變量內聯化)
- Replace Temp with Query(以查詢取代臨時變量)
- Introduce Explaining Variable(引入解釋性變量)
- Split Temporary Variable(剖解臨時變量)
- Remove Assignments to Parameters(移除對參數的賦值動作)
- Replace Method with Method Object(以函數對象取代函數)
- Substitute Algorithm(替換你的算法)
- 章節七 在對象之間搬移特性
- Move Method(搬移函數)
- Move Field(搬移值域)
- Extract Class(提煉類)
- Inline Class(將類內聯化)
- Hide Delegate(隱藏「委托關系」)
- Remove Middle Man(移除中間人)
- Introduce Foreign Method(引入外加函數)
- Introduce Local Extension(引入本地擴展)
- 章節八 重新組織數據
- Self Encapsulate Field(自封裝值域)
- Replace Data Value with Object(以對象取代數據值)
- Change Value to Reference(將實值對象改為引用對象)
- Replace Array with Object(以對象取代數組)
- Replace Array with Object(以對象取代數組)
- Duplicate Observed Data(復制「被監視數據」)
- Change Unidirectional Association to Bidirectional(將單向關聯改為雙向)
- Change Bidirectional Association to Unidirectional(將雙向關聯改為單向)
- Replace Magic Number with Symbolic Constant(以符號常量/字面常量取代魔法數)
- Encapsulate Field(封裝值域)
- Encapsulate Collection(封裝群集)
- Replace Record with Data Class(以數據類取代記錄)
- Replace Type Code with Class(以類取代型別碼)
- Replace Type Code with Subclasses(以子類取代型別碼)
- Replace Type Code with State/Strategy(以State/strategy 取代型別碼)
- Replace Subclass with Fields(以值域取代子類)
- 章節九 簡化條件表達式
- Decompose Conditional(分解條件式)
- Consolidate Conditional Expression(合并條件式)
- Consolidate Duplicate Conditional Fragments(合并重復的條件片段)
- Remove Control Flag(移除控制標記)
- Replace Nested Conditional with Guard Clauses(以衛語句取代嵌套條件式)
- Replace Conditional with Polymorphism(以多態取代條件式)
- Introduce Null Object(引入Null 對象)
- Introduce Assertion(引入斷言)
- 章節十一 處理概括關系
- Pull Up Field(值域上移)
- Pull Up Method(函數上移)
- Pull Up Constructor Body(構造函數本體上移)
- Push Down Method(函數下移)
- Push Down Field(值域下移)
- Extract Subclass(提煉子類)
- Extract Superclass(提煉超類)
- Extract Interface(提煉接口)
- Collapse Hierarchy(折疊繼承關系)
- Form Template Method(塑造模板函數)
- Replace Inheritance with Delegation(以委托取代繼承)
- Replace Delegation with Inheritance(以繼承取代委托)
- 章節十二 大型重構
- 這場游戲的本質
- Tease Apart Inheritance(梳理并分解繼承體系)
- Convert Procedural Design to Objects(將過程化設計轉化為對象設計)
- Separate Domain from Presentation(將領域和表述/顯示分離)
- Extract Hierarchy(提煉繼承體系)
- 章節十三 重構,復用與現實
- 現實的檢驗
- 為什么開發者不愿意重構他們的程序?
- 現實的檢驗(再論)
- 重構的資源和參考資料
- 從重構聯想到軟件復用和技術傳播
- 結語
- 參考文獻
- 章節十四 重構工具
- 使用工具進行重構
- 重構工具的技術標準(Technical Criteria )
- 重構工具的實用標準(Practical Criteria )
- 小結
- 章節十五 集成
- 參考書目