創(chuàng)建可維護(hù)和可測(cè)試的 Windows 窗體應(yīng)用程序的 10 種方法(譯)
我遇到的大多數(shù) Windows 窗體應(yīng)用程序都不存在或單元測(cè)試覆蓋率極低。而且它們通常也很難維護(hù),項(xiàng)目中各種 Form 類的代碼背后有數(shù)百甚至數(shù)千行代碼,但它不必是這樣。僅僅因?yàn)?Windows 窗體是一項(xiàng)“遺留”技術(shù),并不意味著你注定會(huì)造成無法維護(hù)的混亂。下面是創(chuàng)建可維護(hù)和可測(cè)試的 Windows 窗體應(yīng)用程序的十個(gè)技巧。
1. 用用戶控件隔離你的用戶界面
首先,避免在一個(gè)表單上放置太多控件。通常,你的應(yīng)用程序的主要形式可以分解為邏輯區(qū)域(我們可以稱之為“視圖”)。如果將這些區(qū)域中的每個(gè)區(qū)域的控件放入它們自己的容器中,那么你自己的生活就會(huì)變得更加輕松,而在 Windows 窗體中,最簡(jiǎn)單的方法是使用用戶控件。因此,如果你有一個(gè)資源管理器樣式的應(yīng)用程序,左側(cè)是樹視圖,右側(cè)是詳細(xì)信息視圖,則將 TreeView 放入其自己的 UserControl,并為每個(gè)可能的右側(cè)視圖創(chuàng)建一個(gè) UserControl。同樣,如果你有選項(xiàng)卡控件,請(qǐng)為選項(xiàng)卡控件中的每個(gè)頁面創(chuàng)建一個(gè)單獨(dú)的 UserControl。
這樣做不僅可以防止你的類變得難以管理,而且還可以調(diào)整大小和設(shè)置Tab 鍵順序等,使任務(wù)變得更加簡(jiǎn)單。它還允許你在必要時(shí)輕松地一次性禁用用戶界面的整個(gè)部分。你還會(huì)發(fā)現(xiàn),當(dāng)你將用戶界面分解為包含邏輯分組控件的較小 UserControl 時(shí),重新設(shè)計(jì)應(yīng)用程序的 UI 布局會(huì)變得更加容易。
2. 將非 UI 代碼排除在后面的代碼之外
在 Windows 窗體應(yīng)用程序中,你總是會(huì)在窗體背后的代碼中找到訪問網(wǎng)絡(luò)、數(shù)據(jù)庫或文件系統(tǒng)的代碼。這嚴(yán)重違反了“單一責(zé)任原則”。你的 Form 或 UserControl 類的重點(diǎn)應(yīng)該只是用戶界面。因此,當(dāng)你檢測(cè)到背后的代碼中存在與 UI 無關(guān)的代碼時(shí),請(qǐng)將其重構(gòu)為具有單一職責(zé)的類。因此,你可以創(chuàng)建一個(gè) PreferencesManager 類,或者一個(gè)負(fù)責(zé)調(diào)用特定 Web 服務(wù)的類。然后可以將這些類作為依賴項(xiàng)注入到你的 UI 組件中(盡管這只是第一步——我們可以進(jìn)一步擴(kuò)展這個(gè)想法,我們很快就會(huì)看到)。
3. 用接口創(chuàng)建被動(dòng)視圖
一種特別有用的技術(shù)是使你創(chuàng)建的每個(gè)窗體和用戶控件都實(shí)現(xiàn)一個(gè)視圖接口。此接口應(yīng)包含允許設(shè)置和檢索視圖中控件的狀態(tài)和內(nèi)容的屬性。它還可能包括報(bào)告用戶交互的事件,例如單擊按鈕或移動(dòng)滑塊。目標(biāo)是這些視圖接口的實(shí)現(xiàn)是完全被動(dòng)的。理想情況下,你的 Forms 和 UserControls 背后的代碼中不應(yīng)該有任何條件邏輯。
下面是一個(gè)用于新用戶條目視圖的視圖接口示例。這個(gè)視圖的實(shí)現(xiàn)應(yīng)該是微不足道的。任何業(yè)務(wù)邏輯都不屬于后面的代碼(我們接下來將討論它屬于哪里)。
interface?INewUserView
{
????string?FirstName?{?get;?set;?}
????string?LastName?{?get;?set;?}
????event?EventHandler?SaveClicked;
}
通過確保你的視圖實(shí)現(xiàn)盡可能簡(jiǎn)單,你將能夠最大程度地遷移到替代 UI 框架(如 WPF),因?yàn)槟阄ㄒ恍枰龅木褪窃谛录夹g(shù)中重新創(chuàng)建視圖。所有其他代碼都可以重復(fù)使用。
4.使用presenters控制視圖
因此,如果你已將所有視圖設(shè)為被動(dòng)并實(shí)現(xiàn)接口,則你需要一些能夠?qū)崿F(xiàn)應(yīng)用程序業(yè)務(wù)邏輯并控制視圖的東西。我們可以稱這些為“presenter”類。這是稱為“模型視圖演示者”或 MVP 的模式。
在模型視圖展示器中,你的視圖是完全被動(dòng)的,展示器會(huì)指示視圖顯示哪些數(shù)據(jù)。還允許視圖與演示者通信。在我上面的示例中,它通過引發(fā)事件來實(shí)現(xiàn),但通常使用這種模式,你的視圖可以直接調(diào)用演示者。
絕對(duì)不允許視圖開始直接操作模型(包括你的業(yè)務(wù)實(shí)體、數(shù)據(jù)庫層等)。如果你遵循 MVP 模式,你的應(yīng)用程序中的所有業(yè)務(wù)邏輯都可以輕松測(cè)試,因?yàn)樗挥?Presenter 或其他非 UI 類中。
5. 為錯(cuò)誤報(bào)告創(chuàng)建服務(wù)
通常,你的演示者類需要顯示錯(cuò)誤消息。但不要只是將 MessageBox.Show 放入非 UI 類中。你將使該方法無法進(jìn)行單元測(cè)試。而是創(chuàng)建一個(gè)服務(wù)(比如 IErrorDisplayService),你的演示者可以在需要報(bào)告問題時(shí)調(diào)用該服務(wù)。這使你的演示者單元保持可測(cè)試性,并且還提供了更改將來向用戶呈現(xiàn)錯(cuò)誤的方式的靈活性。
6. 使用命令模式
如果你的應(yīng)用程序包含一個(gè)帶有大量按鈕供用戶單擊的工具欄,則命令模式可能非常適合。命令模式規(guī)定你為每個(gè)命令創(chuàng)建一個(gè)類。這有很大的好處,可以將你的代碼分成小類,每個(gè)小類都有一個(gè)責(zé)任。它還允許你集中處理與特定命令有關(guān)的所有事情。是否應(yīng)該啟用該命令?它應(yīng)該是可見的嗎?它的工具提示和快捷鍵是什么?它是否需要特定的特權(quán)或許可才能執(zhí)行?命令運(yùn)行時(shí)拋出的異常應(yīng)該如何處理?
命令模式允許你標(biāo)準(zhǔn)化處理應(yīng)用程序中所有命令所共有的每個(gè)問題的方式。你的命令對(duì)象將有一個(gè) Execute 方法,該方法實(shí)際上包含為該命令執(zhí)行所需行為的代碼。在許多情況下,這將涉及調(diào)用其他對(duì)象和業(yè)務(wù)服務(wù),因此你需要將它們作為依賴項(xiàng)注入到命令對(duì)象中。你的命令對(duì)象本身應(yīng)該可以(并且直接)進(jìn)行單元測(cè)試。
7. 使用 IoC 容器管理依賴項(xiàng)
如果你正在使用 Presenter 類和 Command 類,那么你可能會(huì)發(fā)現(xiàn)它們所依賴的類的數(shù)量隨著時(shí)間的推移而增長(zhǎng)。這是Unity或StructureMap等控制反轉(zhuǎn)容器真正可以幫助你的地方。無論它們具有多少級(jí)別的依賴關(guān)系,它們都允許你輕松構(gòu)建視圖和演示器。
8. 使用事件聚合器模式
另一種在 Windows 窗體應(yīng)用程序中非常有用的設(shè)計(jì)模式是事件聚合器模式(有時(shí)也稱為“信使”或“事件總線”)。這是一種模式,其中事件的引發(fā)者和事件的處理者根本不需要相互耦合。當(dāng)你的代碼中發(fā)生需要在其他地方處理的“事件”時(shí),只需向事件聚合器發(fā)布一條消息即可。然后需要響應(yīng)該消息的代碼可以訂閱和處理它,而無需擔(dān)心是誰提出的。
例如,你發(fā)送一條“請(qǐng)求幫助”消息,其中包含用戶當(dāng)前在 UI 中的位置的詳細(xì)信息。然后另一個(gè)服務(wù)處理該消息并確保在 Web 瀏覽器中啟動(dòng)幫助文檔中的正確頁面。另一個(gè)例子是導(dǎo)航。如果你的應(yīng)用程序有多個(gè)屏幕,則可以將“導(dǎo)航”消息發(fā)布到事件聚合器,然后訂閱者可以通過確保新屏幕顯示在用戶界面中來響應(yīng)該消息。
除了從根本上分離事件的發(fā)布者和訂閱者之外,事件聚合器還具有創(chuàng)建極易進(jìn)行單元測(cè)試的代碼的巨大好處。
9. 使用 Async 和 Await 進(jìn)行線程處理
如果你的目標(biāo)是 .NET 4 及更高版本并使用 Visual Studio 12 或更高版本,請(qǐng)不要忘記你可以使用新的 async 和 await 關(guān)鍵字,這將大大簡(jiǎn)化應(yīng)用程序中的任何線程代碼,并自動(dòng)處理回送后臺(tái)任務(wù)完成后進(jìn)入 UI 線程。它們還極大地簡(jiǎn)化了跨多個(gè)鏈?zhǔn)胶笈_(tái)任務(wù)的異常處理。它們非常適合 Windows 窗體應(yīng)用程序,如果你還沒有的話,非常值得一試。
10.不要太晚
可以將我上面描述的所有模式和技術(shù)改造為現(xiàn)有的 Windows 窗體應(yīng)用程序,但我可以從痛苦的經(jīng)驗(yàn)告訴你,這可能需要大量工作,尤其是當(dāng)窗體背后的代碼達(dá)到數(shù)千行時(shí)。如果你開始使用 MVP、事件聚合器和命令模式等模式構(gòu)建應(yīng)用程序,你會(huì)發(fā)現(xiàn)隨著它們變得越來越大,維護(hù)起來會(huì)少很多痛苦。你還可以對(duì)所有業(yè)務(wù)邏輯進(jìn)行單元測(cè)試,這對(duì)于持續(xù)的可維護(hù)性至關(guān)重要。
原文作者:Mark Heath
原文鏈接:https://markheath.net/post/maintainable-winforms
轉(zhuǎn)載自微信公眾號(hào):OneByOneDotNet
公眾號(hào)文章鏈接:https://mp.weixin.qq.com/s/ks_ghCRxMmOQPYFib0cb3g
