概述
當一個方法包含大量的布爾參數時,方法是很脆弱的,由此還可能會產生兩個問題:
2. 給方法的使用者造成一定困擾,可能會產生一些預期之外的結果
本文要介紹的重構策略“為布爾方法命名”,可以有效地避開這兩個問題。
為布爾方法命名
大量布爾參數帶來的問題
下圖中的SomeClass的SomeMethod包含3個布爾參數,如果沒有注釋,調用者根本不知道3個布爾參數所代表的含義。
即使為這個方法提供了詳細的注釋,調用者也很容易在調用時出錯。
調用者一不小心寫錯了其中一個參數,就可能產生非預期的行為。
在實際的業務處理中,也許你僅用這3個參數處理2~3個業務Case。
但這個方法有3個布爾參數,相當於為調用者提供了8種調用Case!
難道你的程序需要提供8種Case嗎?完全沒有必要,因為其他幾種Case可能根本不會發生!
var obj = new SomeClass(); obj.SomeMethod(false, false, false); // way1 obj.SomeMethod(false, true, false); // way2 obj.SomeMethod(false, true, true); // way3 obj.SomeMethod(false, false, true); // way4 obj.SomeMethod(true, true, true); // way5 obj.SomeMethod(true, false, false); // way6 obj.SomeMethod(true, true, false); // way7 obj.SomeMethod(true, false, true); // way8
這好比上個世紀生產的電視機遙控器,幾十個按鍵,用戶沒有說明書根本就不知道怎么用!
對於用戶來說,一個遙控器用來調節頻道、音量就足夠了。
給用戶太多地選擇反而不是一件好的事情,用戶會因太多的選擇而徘徊不定,永遠不要給用戶提供繁雜的使用方式!
重構策略
“為布爾方法命名”這種策略,意在將一些布爾參數提取出來,結合適當的方法命名來取代布爾參數。
接下來,進入我們的示例環節,用實際的示例來說明這個重構策略。
示例
重構前
CreateAccount()方法用於創建賬戶,它提供了4個參數,后3個參數都是布爾類型的。
public class BankAccount { public void CreateAccount(Customer customer, bool withChecking, bool withSavings, bool withStocks) { // do work } }
如果你是這個方法的調用者,在你腦袋中或許會蹦出8種調用方式,但你知道該如何調用嗎?
我不知道你們的答案是什么,我的答案是:不明確。
重構后
仔細分析“創建銀行賬戶”這項業務,我們發現“創建賬戶”只會產生兩種Case。
PS: 你去銀行開戶時,需要驗證你的身份信息。
2. 驗證並創建賬戶,同時存入初始儲蓄金額
PS: 你去銀行開戶時,當身份信息驗證通過后,你可以向銀行卡中存入一部分初始金額。
我們提供了2個方法來實現這2種Case,這2個方法使用具有意義的命名,並將原有的CreateAccount()方法用private修飾限制其訪問性。
public class BankAccount { public void CreateAccountWithChecking(Customer customer) { CreateAccount(customer, true, false); } public void CreateAccountWithCheckingAndSavings(Customer customer) { CreateAccount(customer, true, true); } private void CreateAccount(Customer customer, bool withChecking, bool withSavings) { // do work } }
重構以后,調用者只能使用CreateAccountWithChecking()和CreateAccountWithCheckingAndSavings()方法,這兩個方法的名稱也很容易讓人知道其含義。
使用Flags
這條建議來自於園友@blackbob。C#的Flags特性允許enum類型使用組合值。
public class BankAccount { private readonly IList<CreateAccountOptions> _createAccountOptionList = new[] { CreateAccountOptions.WithChecking, CreateAccountOptions.WithChecking | CreateAccountOptions.WithSavings }; public void CreateAccount(Customer customer, CreateAccountOptions option) { if (!_createAccountOptionList.Contains(option)) { throw new ArgumentException("Invalid create account option", "option"); } // do work } } [Flags] public enum CreateAccountOptions { None = 0, WithChecking = 1, WithSavings = 2, WithStocks = 4 } class Program { static void Main(string[] args) { BankAccount bankAccount = new BankAccount(); Customer customer1 = new Customer(); // 驗證並創建賬戶 bankAccount.CreateAccount(customer1, CreateAccountOptions.WithChecking); // 驗證並創建賬戶,同時存入初始儲蓄金額 Customer customer2 = new Customer(); bankAccount.CreateAccount(customer2, CreateAccountOptions.WithChecking | CreateAccountOptions.WithSavings); } }
使用Flags枚舉,除了例子中的兩種Case外,還可能產生一些其他的組合。
為了防止用戶濫傳參數,我們可以在執行CreateAccount操作前先做個參數驗證。