掌握DRY原則:在C#中應用設計模式以獲得更乾淨的代碼
C#中的設計模式是撰寫高效、可重用和可維護程式碼的基本工具。 這些模式提供了對常見軟體設計問題的標準解決方案,推廣最佳實踐並幫助開發者避免冗餘程式碼。 應用設計模式的核心原則之一是DRY(Don't Repeat Yourself)原則,強調降低程式碼中的重複性以提高可讀性和可維護性。
本文靈感來自Tim Corey的深入見解視頻,"設計模式:不要在C#中重複自己",深入探討DRY原則及其在創建更整潔、更有組織的程式碼中的實際應用。 通過探索Tim視頻中討論的關鍵概念和策略,本文旨在為您提供一個全面的指南,以有效地在您的C#專案中實施DRY設計模式原則。
Introduction to the DRY Principle in C
在此介紹中,Tim Corey解釋了DRY原則,即"Don't Repeat Yourself"。這一原則是編程中的基本概念,強調通過確保每個知識或邏輯在程式碼中的唯一表示來避免冗餘。 Tim通過一個簡單的WinForms應用程序示例來說明這一原則。 該表單包括輸入名字和姓氏的字段,還有一個按鈕用來基於這些字段生成員工ID。
識別並預判程式碼重複
在(0:53)處,Tim開始識別並預判程式碼中的重複。 他使用WinForms應用程序的示例說明了即使方法僅被調用一次,重複如何發生。 在應用程式中,員工ID的生成邏輯涉及從名字和姓氏的文本字段中提取子字符串並在末尾添加3位數代碼。

在上面的截圖中(1:31),Tim演示了應用程序的功能,顯示它如何通過將名字和姓氏的前四個字母與三位代碼結合來生成員工ID。 他強調,儘管這段程式碼似乎遵循DRY原則,因為它沒有顯式重複相同的邏輯,但這段重複模式存在潛在問題需要解決。
在(1:51),他指出,儘管這段程式碼看起來很簡單,但它並未完全遵循DRY原則,因為生成員工ID的邏輯與按鈕的點擊事件緊密耦合。 這意味著如果客戶端程式碼的其他地方需要此邏輯,例如在處理新員工列表時(3:58),則需要重複或適應程式碼,這會導致冗餘。
創建獨立的、可重用的方法
在這一部分中,Tim Corey演示了如何創建獨立的、可重用的方法以遵循DRY原則。 他首先將生成員工ID的邏輯從事件處理程序中提取出來,成為一個單獨的方法。 這一重構涉及創建一個名為GenerateEmployeeID的私有方法,並將現有程式碼移動到此方法中(5:15)。 修訂後的事件處理程序程式碼只需調用這一方法。
步驟和示例:
-
初始程式碼:生成員工ID的邏輯直接位於按鈕的點擊事件處理程序中。

-
重構程式碼:Tim通過使其更靈活來提高方法的改進。 方法不再依賴於特定的UI元素,現在接受
lastName作為參數並返回生成的ID。 此更改允許該方法在各種上下文和UI元素中使用:private string GenerateEmployeeID(string firstName, string lastName) { string employeeID = firstName.Substring(0, 4) + lastName.Substring(0, 4) + DateTime.Now.Millisecond.ToString(); return employeeID; }private string GenerateEmployeeID(string firstName, string lastName) { string employeeID = firstName.Substring(0, 4) + lastName.Substring(0, 4) + DateTime.Now.Millisecond.ToString(); return employeeID; }Tim然後展示如何從點擊事件中調用此方法:
employeeIdText.Text = GenerateEmployeeID(firstNameText.Text, lastNameText.Text);employeeIdText.Text = GenerateEmployeeID(firstNameText.Text, lastNameText.Text);他還提到,現在這個方法可以在應用程式的其他部分使用,例如在處理包含多個員工紀錄的CSV文件時,而無需重複程式碼。
構建和使用類程式庫
然後Tim Corey探索了類程式庫的概念以進一步提升程式碼重用性和可維護性。 他說明了如何封裝GenerateEmployeeID方法到一個類程式庫對象中,可以在多個專案中使用。
在(8:00)處,Tim解釋說,設計會根據用戶或公司政策的需求不斷改變,以增加與圖形和動畫互動的可能性。 因此,他在解決方案中引入了一個WPF專案,具有完全相同的字段和一個生成員工ID的按鈕。
Tim在(9:15)處,提出了使用類程式庫的強力論點,認為如果我們要避免重複自己,那麼程式碼就必須在新的WPF專案中進行複製和粘貼。 因此,為了保持DRY,我們需要在類程式庫中創建類。
步驟和示例:
-
創建類程式庫:
-
Tim在(9:47)創建了一個新的.NET Framework類程式庫專案,命名為DRYDemoLibrary。
-
在此程式庫中,他定義了一個公共類
GenerateEmployeeID方法移動到此類中:public class EmployeeProcessor { public string GenerateEmployeeID(string firstName, string lastName) { string employeeID = firstName.Substring(0, 4) + lastName.Substring(0, 4) + DateTime.Now.Millisecond.ToString(); return employeeID; } }public class EmployeeProcessor { public string GenerateEmployeeID(string firstName, string lastName) { string employeeID = firstName.Substring(0, 4) + lastName.Substring(0, 4) + DateTime.Now.Millisecond.ToString(); return employeeID; } }
-
-
在專案中使用類程式庫:
-
在他的WinForms(13:18)和WPF專案(14:00)中,Tim添加了一個到DRYDemoLibrary類程式庫的引用。
-
然後他用來自類程式庫的
GenerateEmployeeID方法調用代替了舊程式碼:EmployeeProcessor processor = new EmployeeProcessor(); employeeIDText.Text = processor.GenerateEmployeeID(firstNameText.Text, lastNameText.Text);EmployeeProcessor processor = new EmployeeProcessor(); employeeIDText.Text = processor.GenerateEmployeeID(firstNameText.Text, lastNameText.Text); - 這種方法消除了冗餘,因為該方法現在只需在一個地方進行維護。 Tim證明相同的類程式庫可以在不同的UI框架(WinForms和WPF)間重複使用而無需重複程式碼。
-
-
優勢:
-
一致性:通過在類程式庫中集中化邏輯,Tim確保對邏輯(例如,修正錯誤)的更改在所有專案中均能一致應用。
- 減少維護:方法中的更改只需在類程式庫中進行,避免不一致並降低維護負擔。
-
將類程式庫整合到多個專案中
Tim Corey繼續研究如何在不同類型的專案中使用DRYDemoLibrary類程式庫,特別是將該程式庫整合到一個新的控制台應用程序中。 這證明該程式庫的功能可以在各種應用程序中重複使用,而不僅僅是一個實例或僅限於同一解決方案。
步驟和示例:
-
創建新解決方案和專案:
-
Tim在(17:29)開始創建一個新解決方案用於控制台應用程序,模擬一個使用DRYDemoLibrary於不同類型專案(如Windows服務或控制台應用程序)的場景。
-
他命名新專案為ConsoleUI並展示如何設置一個基本控制台應用程序。
class Program { static void Main(string[] args) { Console.ReadLine(); } }class Program { static void Main(string[] args) { Console.ReadLine(); } }
-
-
添加對類程式庫的引用:
-
Tim解釋如何在新專案中添加對DRYDemoLibrary DLL的引用。 這包括瀏覽到類程式庫專案bin文件夾中的DLL文件,並將其添加到控制台應用程序。
using DRYDemoLibrary;using DRYDemoLibrary; -
添加引用後,Tim(19:24)使用程式庫中的EmployeeProcessor類生成基於用戶輸入的員工ID。
Console.WriteLine("What is your first name?"); string firstName = Console.ReadLine(); Console.WriteLine("What is your last name?"); string lastName = Console.ReadLine(); EmployeeProcessor processor = new EmployeeProcessor(); string employeeID = processor.GenerateEmployeeID(firstName, lastName); Console.WriteLine($"Your employee ID is {employeeID}");Console.WriteLine("What is your first name?"); string firstName = Console.ReadLine(); Console.WriteLine("What is your last name?"); string lastName = Console.ReadLine(); EmployeeProcessor processor = new EmployeeProcessor(); string employeeID = processor.GenerateEmployeeID(firstName, lastName); Console.WriteLine($"Your employee ID is {employeeID}");
-
-
運行控制台應用程序:
-
Tim展示運行控制台應用程序,以顯示它成功使用該程式庫生成員工ID。 這確認相同的程式庫程式碼可以在不同專案中重複使用。

-
-
更新DLL:
- Tim簡要提到,如果DLL更改,可以在引用它的專案中進行更新。 他指出,儘管本視頻未詳細介紹,但使用NuGet包是管理和更新多個專案中DLL的推薦方法。
更新DLL和管理NuGet套件
Tim Corey簡要介紹了使用NuGet包來管理和更新類程式庫的概念。 這種方法提供了一個更具可擴展性的解決方案,用於處理依賴關係和更新,特別是在較大的專案或組織中。
關鍵點:
-
創建一個NuGet包:
- Tim建議將類程式庫創建為NuGet包,以替代手動管理DLL文件。 這涉及將DLL包裝成NuGet包並上傳到NuGet伺服器(私有或公開)。
-
更新包:
- 通過使用NuGet包,您可以在引用它的所有專案中更新程式庫,只需更新包版本即可。 這能確保一致性並減少版本不匹配或遺漏更新的風險。
-
好處:
-
集中管理:NuGet包提供了一種集中管理程式庫版本和依賴關係的方法。
-
更新便利性:使在多個專案中更新程式庫變得更容易且更可靠。
- 整合:NuGet與各種開發工具和環境整合,簡化了管理程式庫依賴關係的過程。
-
在單元測試中實施DRY:速成課程
在這一部分,Tim Corey展示了如何運用DRY(Don't Repeat Yourself)原則來提升單元測試。 他展示了如何在開發工作中運用DRY原則,特別是專注於單元測試。
初始測試設置
Tim一開始運行一個目前因DLL中的錯誤而失敗的單元測試。 他強調單元測試在識別問題方面的重要性,甚至當程式碼不在主要解決方案中時。 程式碼預期一個4個字母的輸入,但Tim卻傳遞了一個3個字母的名字,這導致即使它未直接包含在解決方案中,DLL文件中的程式崩潰。

重構程式碼以解決錯誤
為了解決名字處理問題,Tim對程式碼進行了重構。 他解釋了如何通過創建新的類程式庫專案(23:50),將DRY應用於開發中。 這種方法確保對多個對象的更改可以一次進行並有效測試,而不需重複修正。

添加單元測試
Tim在類程式庫專案中引入了一個新的測試類名為EmployeeProcessorTest,並使用XUnit設置了單元測試。 他展示了如何創建一個生成員工ID的測試方法,並討論了模擬依賴關係而不是依賴實際值的重要性。

編寫測試方法
Tim編寫了一個名為GenerateEmployeeID_ShouldCalculate的單元測試方法。 他設置了一個帶內聯數據的理論來測試不同場景,確保方法返回預期結果。 他還解釋了如何使用Assert.Equal來驗證輸出。
public class EmployeeProcessorTest
{
[Theory]
[InlineData("Timothy", "Corey", "TimoCore")]
public void GenerateEmployeeID_ShouldCalculate(string firstName, string lastName, string expectedStart)
{
// Arrange
var processor = new EmployeeProcessor();
// Act
var actualStart = processor.GenerateEmployeeID(firstName, lastName).Substring(0, 8);
// Assert
Assert.Equal(expectedStart, actualStart);
}
}
public class EmployeeProcessorTest
{
[Theory]
[InlineData("Timothy", "Corey", "TimoCore")]
public void GenerateEmployeeID_ShouldCalculate(string firstName, string lastName, string expectedStart)
{
// Arrange
var processor = new EmployeeProcessor();
// Act
var actualStart = processor.GenerateEmployeeID(firstName, lastName).Substring(0, 8);
// Assert
Assert.Equal(expectedStart, actualStart);
}
}
運行單元測試
Tim強調對動態數據,如日期時間值的模擬以控制測試條件和結果的重要性。 他討論了處理動態字符串的挑戰,以及如何通過使用控制的值來測試不同的場景。 然後他運行了單元測試,但在這之前,他添加了兩個必須運行測試的NuGet套件:xunit.runner.visualstudio。

成功運行所有測試之後一個內聯數據的輸出結果如下所示:

現今於(31:30),Tim添加了另一個內聯數據,並將子字串的第二個參數更改為expectedStart.Length。
public class EmployeeProcessorTest
{
[Theory]
[InlineData("Timothy", "Corey", "TimoCore")]
[InlineData("Tim", "Corey", "TimCore")]
public void GenerateEmployeeID_ShouldCalculate(string firstName, string lastName, string expectedStart)
{
var processor = new EmployeeProcessor();
var actualStart = processor.GenerateEmployeeID(firstName, lastName).Substring(0, expectedStart.Length);
Assert.Equal(expectedStart, actualStart);
}
}
public class EmployeeProcessorTest
{
[Theory]
[InlineData("Timothy", "Corey", "TimoCore")]
[InlineData("Tim", "Corey", "TimCore")]
public void GenerateEmployeeID_ShouldCalculate(string firstName, string lastName, string expectedStart)
{
var processor = new EmployeeProcessor();
var actualStart = processor.GenerateEmployeeID(firstName, lastName).Substring(0, expectedStart.Length);
Assert.Equal(expectedStart, actualStart);
}
}
在(32:05)再次運行單元測試後,第二個理論導致測試失敗:

通過私有方法改進程式碼
為遵循DRY,Tim進一步通過在DRYDemoLibrary下的實際GetPartOfName來對程式碼進行了重構。 這一方法處理名稱部分的提取,提升程式碼重用性和可讀性。 Tim做了以下更改:
public string GenerateEmployeeID(string firstName, string lastName)
{
string employeeID = $@"{GetPartOfName(firstName, 4)}{GetPartOfName(lastName, 4)}{DateTime.Now.Millisecond.ToString()}";
return employeeID;
}
private string GetPartOfName(string name, int numberOfCharacters)
{
string output = name;
if (name.Length > numberOfCharacters)
{
output = name.Substring(0, numberOfCharacters);
}
return output;
}
public string GenerateEmployeeID(string firstName, string lastName)
{
string employeeID = $@"{GetPartOfName(firstName, 4)}{GetPartOfName(lastName, 4)}{DateTime.Now.Millisecond.ToString()}";
return employeeID;
}
private string GetPartOfName(string name, int numberOfCharacters)
{
string output = name;
if (name.Length > numberOfCharacters)
{
output = name.Substring(0, numberOfCharacters);
}
return output;
}
更新單元測試
Tim更新了單元測試以反映程式碼中的更改,例如修改子字串的預期長度。 他解釋了如何通過運行這些測試來快速識別問題,並驗證程式碼是否符合新要求。 Tim添加了新理論,然後運行單元測試以驗證輸出是否符合預期:

擴大.NET標準程式庫的多樣性
創建.NET標準程式庫
為提升您的類程式庫的多樣性,Tim Corey建議從.NET Framework類程式庫過渡到.NET標準類程式庫。 此更改使程式庫可以在各種平台上兼容,包括:
- Windows平台:WinForms、WPF和控制台應用程序
- 跨平台:.NET Core, Xamarin (適用於iOS和Android), Linux和macOS
創建.NET標準程式庫的步驟:
- 添加新專案:右鍵點擊您的解決方案並選擇添加新專案。
-
選擇.NET標準:選擇.NET標準而不是.NET Framework類程式庫。 這種程式庫類型支持多種平台。

- 程式碼遷移:將現有程式碼(例如,EmployeeProcessor類)複製並粘貼到新的.NET標準程式庫中。 這個過程可能涉及一些微調,但核心邏輯保持一致。
通過轉換為.NET標準,您可以使程式庫從多個平台訪問,減少不同應用類型中的程式碼重複並節省開發精力。
避免程式碼和測試中的重複
減少開發中的重複
Tim Corey強調,通過採用.NET標準程式庫,您不僅可減少程式碼庫中的程式碼重複,還可以在開發過程中減少重複。 與其在不同的平台專案中重複程式碼,不如將其集中在一個在多個環境中工作的單一程式庫中。
好處:
- 統一的程式碼庫:一個程式碼庫對於多個平台減少了維護和更新程式碼所需的工作量。
- 簡化測試:使用.NET標準程式庫,您可以一次編寫單元測試,並確保它們適用於所有支持的平台。
測試和調試:Tim將單元測試引入作為進一步減少努力和重複的方法。 自動化測試驗證了程式碼的正確性,而無需手動測試每次應用程序的迭代。
應用DRY的技巧:知道何時止步
Tim Corey強調,雖然遵循DRY(Don't Repeat Yourself)原則對於撰寫可維護的程式碼至關重要,但知道何時何地應用它同樣重要。 並不是每個場景需要採用相同的方法,因此這裡有一些實用的技巧靈感來自Tim的見解:
-
避免在代碼背後和UI中的程式碼:Tim建議不要將邏輯直接放在代碼背後的文件或用戶界面中。 例如,不應將業務邏輯嵌入在表單或按鈕點擊事件中。 相反,將這些邏輯保存在單獨的類或程式庫中。 這種分離有助於保持清晰的架構,使您的程式碼在不同的用戶界面中更易重用。
-
利用.NET標準程式庫:當創建程式庫時,Tim建議盡可能使用.NET標準程式庫而不是.NET Framework程式庫。 NET標準程式庫更具多樣性,允許您的程式碼在包括.NET Core, Xamarin等不同平台上使用。 這種方法減少了程式碼重複並提升程式碼的可移植性。
-
分離平台專用程式碼:由於平台特定要求,例如文件處理或配置管理,有些程式碼可能不適合.NET標準程式庫。 Tim建議在這種情況下創建兩個程式庫:一個用於.NET標準程式碼,另一個用於平台專用程式碼。 這樣,您仍然可以重用核心邏輯,同時滿足平台特定需求。
-
強調單元測試:Tim強烈鼓勵為程式碼編寫單元測試。 單元測試幫助及早發現錯誤並確保您的程式碼符合預期行為。 它們可以大幅加速調試過程,因為您可以快速驗證更改,而無需手動測試整個應用程序。
- 考慮專案規模:對於非常小型或實驗性的專案,Tim承認建立獨立程式庫和進行全面單元測試的成本可能不必要。 然而,對於生產應用,從清晰的架構和單元測試開始是可取的,因為小型專案經常隨著時間的推移而增長和演變。
遵循這些提示,您可以有效地應用DRY原則,同時兼顧程式碼重用和可維護性方面的實際考慮。
結束語
通過設計模式掌握DRY原則對於撰寫乾淨和可維護的C#代碼至關重要。 正如Tim Corey所展示的,有效應用DRY涉及創建可重用的方法、利用類程式庫,以及接受.NET標準以獲得更廣泛的兼容性。 通過了解何時以及如何應用這些實踐,您可以顯著提高程式碼的質量和靈活性。
如需更深入的見解,請查看Tim Corey在此主題上的視頻此處。 欲獲得Tim最新的內容,請訪問他的YouTube頻道。


