讓 ASP.NET WebForm 頁面好還要更好 (一) - 職責分離 (外篇)

程式是抽象的,透過圖形,試著表達從 0~0.8 的重構之路。

[.NET]快快樂樂學LINQ系列前哨戰-var與匿名型別

在進入正篇之前,我覺得該透過圖示,讓大家瞭解自己到底練習了什麼。根據英國研究,人類對圖形的記憶能力,比起文字可是高達數十倍呢。

首先看看最早的版本 (0) 概念上長怎樣:

這時期所有頁面都直指 DB ,也沒有任何的控制程式碼,一切依靠控制項的設定。

我假設讀取產品清單的只有四個頁面,一旦需要更改欄位時,就有四頁的控制項欄位 + SQL 語法 + 參數要跟著變動。

 

這時候我們重構了,進入 0.1 ,每個頁面都有自己的程式,這時期圖形大概長這樣。

真的有寫程式,控制項要進行任何變更都得經過我們程式的處理,而不是傻傻地讓控制項直接存取 DB 。壞處是看起來程式變多了,原本控制項對 DB 處理得很棒,我們都不需要傷腦筋,現在得自己寫了;好處則是控制能力的提升,今天我要回傳清單就回傳,要在回傳前改值就改值,而無需透過許多控制項的事件。另外在此版本程式中,加入了 Try Catch ,一旦發生程式未事前想到的意外, Catch 會協助捕捉;以此稍微提昇了程式的強韌性。

 

再來到了 0.3 ,加入 DataAccess ,圖形大概如下:

此階段,頁面已不再自己連線至 DB ,而是透過 DataAccess 做中間人,取得執行結果就好了,這也是很基本的概念:「資料存取層」,讓 UI 和資料庫分離,當出問題時就比較容易釐清。我們終於可以每次很專心撰寫 UI 程式、資料庫程式了,而不再混雜不清。控制項程式和資料存取程式混在一起,這絕對會讓複雜度 UP 。

說到這裡,可別以為我反對使用控制項,其實恰恰相反,我是贊成多用控制項一派的。控制項代表對環境的抽象及封裝,當環境或需求有些許變動時,它讓開發者透過簡單的設定變更,即可適應。我也為我們公司開發了控制項。

從這個眼光來看,控制項宛如開發者的救贖一般,但我絕對反對一切依靠它。控制項的主戰場是在 UI 上。 UI 的特性是變更快速且難以正規化,將這類需求都寫成程式的話,容易引發程式碼的頻繁變更。相對於 UI ,通常商業邏輯處、底層架構這些不可視的元素,在系統中是相對穩固,不容易變更的。透過封裝成控制項,只需要少量修改屬性就可應付頻繁的變更,其實是好物。

 

再來進入了 0.4 ,透過泛型將自訂型別回傳,此時長這樣:

加入 ProductDataValueObject 來承載資料,並將 DataAccess.getProducts 改為回傳泛型清單。這個修改很小,但每個頁面都因為型別不同,得去改兩行。以複雜度來說很低,可是費工不少。當然,透過 var 宣告,我們可以小幅避免這個問題,但身為工程師,你一定得瞭解這背後代表什麼。

資料存取層已經不再使用弱型別,倘若程式修改過程中, ProductType 被誤寫為不正確的名稱,編譯器會馬上拋錯誤,依循錯誤軌跡可以快速找到修改處。

有趣的是,這是種雙向保護機制,資料庫其實也常變動,透過這層保護,如果只是改個欄位名稱、修改欄位型別,我們可以透過修改資料容器的 Property 來解決。當資料庫調整欄位時,有時 UI 程式不一定需要改。

 

進入了 0.65 ,我們補上讀取單筆資料以及更新的程式,此時圖形如下:

updateProduct 參數很多,會撐破圖形,所以就不明列細節。可以看到查詢單筆也是回傳資料容器,而更新產品卻採用傳遞數個參數,這寫法其實還不太漂亮,之後會在第三篇提到該怎麼做比較好。

從架構的角度來說,這些職責被明確拆分出來後,頁面將不再管理資料庫的任何功能。資料庫任何變動都是相依於 DataAccess 這物件, UI 處理好自己該做的事就好,也就是專注於使用者互動上,以及呼叫 DataAccess。這個好處也可以在 0.7 看到。

 

隨著功能的完整,程式也進入 0.7 ,資料庫控制方法從產品方法中被抽離,圖形如下:

將資料庫處理程式 (executeCmd, readCmd) 抽出,若類別中再加入「增加產品」「刪除產品」「修改產品的長寬高」這些功能時,無需為每個功能都複製一份資料庫處理的程式,功能僅專注在自己完成任務所需的最小修改。

如 0.65 所述, 0.7 這個變更進行時, UI 一概不需要知道。 UI 一樣呼叫查詢清單、查詢單筆、修改產品,馬照跑舞照跳,它不曉得 DataAccess 已經被動了大手術。

看到這裡,程式設計領域中的名言「高內聚、低耦合」被部份達成了,今天一段程式修改時,外界呼叫處修改的越少,代表耦合越低,越不會牽一髮而動全身。

 

隨即進入了 0.8 ,我們將資料庫存取層正式從商業邏輯層抽出,圖形如下:

原本的資料存取層,因為現在只專心處理商業邏輯 (從哪裡取得資料) ,所以被更名為較接近用途的名稱。而 DataAccess 則是換到另一個類別中。

當增加商業邏輯時,往往會再增加類別,而非把原類別不斷長大,此時如果大家都在呼叫 ProductManager.executeCmd 就感覺很好笑,明明今天我在維護工廠資料清單啊,幹嘛呼叫產品管理者呢?

 

好了,到這裡已經把 0~0.8 都圖形表達了,謝謝大家看我的文章。下一篇就進入本篇第三節囉。