C#重構經典全面匯總
1、 封裝集合
概念:本文所講的封裝集合就是把集合進行封裝,僅僅提供調用端須要的接口。
正文:在非常多時候,我們都不希望把一些不必要的操作暴露給調用端,僅僅須要給它所須要的操作或數據即可,那么做法就是封裝。這個重構在微軟的代碼庫也常常遇到。
比方最經典的屬性對字段的封裝就是一個非常好的樣例,那么以下我們將看到對集合的封裝。例如以下代碼所看到的,調用端僅僅須要一個集合的信息,而我們則提供了一個IList的集合。大家都知道IList具有對集合的全部操作,所以這會帶來非常多隱患。最好的做法就是對它進行重構。
| using System.Collections.Generic; |
那么重構之后,我們把IList換成了IEnumerable,大家都知道僅僅包含一個返回值為IEnumerator的GetEnumerator()方法,所以這樣僅僅能遍歷取出它的值。而不能對這個集合做出改變,這正是我們所須要的結果,詳細代碼例如以下:
| using System.Collections.Generic; |
總結:這個樣例非常easy讓我們想到曾經系統間耦合常喜歡用數據庫。每一個系統都會操作數據庫。而且有些系統還會對數據庫的表結構或字段進行改動。那么這非常easy就會造成維護的地獄,非常明智的一個做法就是使用SOA來隔開這些耦合,讓一些僅僅須要數據展示的系統得到自己須要的數據就可以。
2、 移動方法
概念:本文所講的移動方法就是方法放在合適的位置(通常指放在合適的類中)。
正文:移動方法是一個非常easy也非經常見的重構,僅僅要是系統就會存在非常多類。那么類里面包含非常多方法。假設一個方法經常被另外一個類使用(比本身的類使用還多)或者這種方法本身就不應該放在這個類里面,那么這個適合應該考慮把它移到合適的類中。代碼例如以下:
| namespace LosTechies.DaysOfRefactoring.MoveMethod.Before
public int AccountAge { get; private set; } public double CalculateInterestRate() if (AccountAge > 10) return 0.05; public class AccountInterest public AccountInterest(BankAccount account) public double InterestRate public bool IntroductoryRate
|
移動以后大家能夠看到BankAccount類的職責也單一,同一時候CalculateInterestRate也放到了常常使用且適合它的類中了,所以此重構是一個比較好的重構。能讓整個代碼變得更加合理。
| namespace LosTechies.DaysOfRefactoring.MoveMethod.After namespace LosTechies.DaysOfRefactoring.MoveMethod.After
|
總結:這個重構法則在非常多時候能讓我們把代碼組織的結構調整得更合理。同一時候也能給以后的維護帶來方便。
3、 提升方法
概念:提升方法是指將一個非常多繼承類都要用到的方法提升到基類中。
正文:提升方法是指將一個非常多繼承類都要用到的方法提升到基類中,這樣就能降低代碼量,同一時候讓類的結構更清晰。例如以下代碼所看到的,Turn方法在子類Car和Motorcycle 都會用到,由於Vehicle 都會有這種方法,所以我們就會想到把它提到基類中。
| namespace LosTechies.DaysOfRefactoring.PullUpMethod.Before
|
重構后的代碼例如以下。那么如今Car 和Motorcycle 都具有Turn這種方法。假設這種方法改動也僅僅須要改動基類就可以,所以給維護和以后的重構帶來了方便。
| namespace LosTechies.DaysOfRefactoring.PullUpMethod.After
|
總結:這個重構要依據詳細情況使用。假設不是每一個子類都有這種方法的話,能夠考慮使用接口或者其它方式。
4、 減少方法
概念:本文中的減少方法和前篇的提升方法整好相反,也就是把個別子類使用到的方法從基類移到子類里面去。
正文:例如以下代碼所看到的,Animal 類中的方法Bark僅僅有在其子類Dog 中使用,所以最好的方案就是把這種方法移到子類Dog 中。
| namespace LosTechies.DaysOfRefactoring.PushDownMethod.Before { public abstract class Animal { public void Bark() { // code to bark } }
public class Dog : Animal { }
public class Cat : Animal { } }
|
重構后的代碼例如以下,同一時候假設在父類Animal 中假設沒有其它的字段或者公用方法的話,能夠考慮把Bark方法做成一個接口。從而去掉Animal 類。
| namespace LosTechies.DaysOfRefactoring.PushDownMethod.After { public abstract class Animal { }
public class Dog : Animal { public void Bark() { // code to bark } }
public class Cat : Animal { } }
|
總結:面向對象三大特征(繼承、封裝、多態)非常多時候能夠幫助我們,但同一時候也可能會造成使用過度或者使用不當,所以怎樣把握好設計。這個就變得至關重要。在什么時候使用繼承的方式,在什么時候使用組合和聚合。接口和繼承類的選擇等久成了我們的重點。
5、 提升字段
概念:本文中的提升字段和前面的提升方法頗為相似,就是把子類公用的字段提升到基類中。從而達到公用的目的。
正文:例如以下代碼所看到的, Account 的兩個子類CheckingAccount 和SavingsAccount 都有minimumCheckingBalance 字段。所以能夠考慮把這個字段提到基類中。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace LosTechies.DaysOfRefactoring.PullUpField.Before
{
publicabstract class Account
{
}
public classCheckingAccount : Account
{
privatedecimal _minimumCheckingBalance = 5m;
}
public classSavingsAccount : Account
{
private decimal_minimumSavingsBalance = 5m;
}
}
重構后的代碼例如以下,這樣提的前提是這些子類有一個基類或者有非常多相似的字段和方法,不然為了一個字段而單獨建立一個抽象類是不可取的,所以這個就須要詳細權衡。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace LosTechies.DaysOfRefactoring.PullUpField.After
{
publicabstract class Account
{
protecteddecimal _minimumBalance = 5m;
}
public classCheckingAccount : Account
{
}
public classSavingsAccount : Account
{
}
}
總結:這個重構的策略比較簡單,同一時候也是比較經常使用的一些做法。最主要就是要注意權衡是否真的有這個必要。看這樣做到底有沒有什么優點(比方僅僅須要改一個地方。維護簡便了,同一時候代碼量也更少了等)。
6、 減少字段
概念:本文中的減少字段和前篇的提升字段正好相反,就是把基類中僅僅有某些少數類用到的字段減少到使用它們的子類中。
正文:例如以下代碼所看到的,基類Task 類中的_resolution字段僅僅會在子類BugTask 中用到。所以就考慮把它放到BugTask 類中。
namespace LosTechies.DaysOfRefactoring.PushDownField.Before
{
publicabstract class Task
{
protectedstring _resolution;
}
public classBugTask : Task
{
}
public classFeatureTask : Task
{
}
}
重構后的代碼例如以下所看到的,這樣做的優點能夠簡化基類。同一時候讓其它沒有使用它的子類也變得更加簡單。假設這種字段比較多的話,使用此重構也能節約一部分內存。
namespace LosTechies.DaysOfRefactoring.PushDownField.After
{
publicabstract class Task
{
}
public classBugTask : Task
{
privatestring _resolution;
}
public classFeatureTask : Task
{
}
}
總結:此重構也是一個非常easy的重構,在非常多時候我們都會不自覺的使用它。
7、 重命名(方法。類。參數)
概念:本文中的改名(方法,類。參數)是指在寫代碼的時候對類、方法、參數、托付、事件等等元素取一個有意義的名稱。
正文:例如以下代碼所看到的,增加一個公司建立一個員工的類。類中有一個員工名字的字段和一個依照小時計算員工收入的方法,那么以下代碼的取名就顯得非常難理解了,所以我們會重構名稱。
namespace LosTechies.DaysOfRefactoring.Rename.Before
{
public classPerson
{
publicstring FN { get; set; }
publicdecimal ClcHrlyPR()
{
//code to calculate hourly payrate
return 0m;
}
}
}
重構后代碼例如以下所看到的,這樣看起來就非常清晰,假設有新進項目組的成員,也會變得非常樂意看這個代碼。
namespace LosTechies.DaysOfRefactoring.Rename.After
{
// Changedthe class name to Employee
publicclass Employee
{
publicstring FirstName { get; set; }
publicdecimal CalculateHourlyPay()
{
//code to calculate hourly payrate
return 0m;
}
}
}
總結:此重構常常被廣大程序猿所忽視。可是帶來的隱患是不可估量的。或許老板要改動功能。那我們來看這段沒有重構的代碼(就算是自己寫的。但因為時間和項目多等關系,我們也非常難理解了)。然后就會變得焦頭爛額。
相反重構后的代碼就會認為一目了然、賞心悅目。
8、 使用委派取代繼承
概念:本文中的“使用委派取代繼承”是指在根本沒有父子關系的類中使用繼承是不合理的,能夠用委派的方式來取代。
例如以下代碼所看到的。Child 和Sanitation(公共設施)是沒有邏輯上的父子關系。由於小孩不可能是一個公共設施吧!所以我們為了完畢這個功能能夠考慮使用委派的方式。
namespace LosTechies.DaysOfRefactoring.ReplaceInheritance.Before
{
public class Sanitation
{
public string WashHands()
{
return "Cleaned!";
}
}
public class Child : Sanitation
{
}
}
重構后的代碼例如以下,把Sanitation 委派到Child 類中,從而能夠使用WashHands這種方法。這樣的方式我們常常會用到,事實上IOC也使用到了這個原理,能夠通過構造注入和方法注入等。
namespace LosTechies.DaysOfRefactoring.ReplaceInheritance.After
{
public class Sanitation
{
public string WashHands()
{
return "Cleaned!";
}
}
public class Child
{
private Sanitation Sanitation { get;set; }
public Child()
{
Sanitation = new Sanitation();
}
public string WashHands()
{
return Sanitation.WashHands();
}
}
}
總結:這個重構是一個非常好的重構。在非常大程度上攻克了濫用繼承的情況,非常多設計模式也用到了這樣的思想(比方橋接模式、適配器模式、策略模式等)。
9、 提取接口
概念:本文中的“提取接口” 是指超過一個的類要使用某一個類中部分方法時,我們應該解開它們之間的依賴,讓調用者使用接口,這非常easy實現也能夠減少代碼的耦合性。
正文:例如以下代碼所看到的。RegistrationProcessor 類僅僅使用到了ClassRegistration 類中的Create方法和Total 字段。所以能夠考慮把他們做成接口給RegistrationProcessor 調用。
namespace LosTechies.DaysOfRefactoring.ExtractInterface.Before
{
public classClassRegistration
{
publicvoid Create()
{
// createregistration code
}
publicvoid Transfer()
{
//class transfer code
}
publicdecimal Total { get; private set; }
}
public classRegistrationProcessor
{
publicdecimal ProcessRegistration(ClassRegistrationregistration)
{
registration.Create();
returnregistration.Total;
}
}
}
重構后的代碼例如以下,我們提取了一個IClassRegistration 接口。同一時候讓ClassRegistration 繼承此接口,然后調用端RegistrationProcessor 就能夠直接通過IClassRegistration 接口進行調用。
namespace LosTechies.DaysOfRefactoring.ExtractInterface.After
{
publicinterface IClassRegistration
{
void Create();
decimal Total{ get; }
}
public classClassRegistration : IClassRegistration
{
publicvoid Create()
{
//create registration code
}
publicvoid Transfer()
{
//class transfer code
}
publicdecimal Total { get; private set; }
}
public classRegistrationProcessor
{
publicdecimal ProcessRegistration(IClassRegistrationregistration)
{
registration.Create();
return registration.Total;
}
}
}
總結:這個重構策略也是一個常見的運用。非常多設計模式也會在當中運用此思想(如簡單project、抽象工廠等都會通過接口來解開依賴)。
10、提取方法
概念:本文中的把某些計算復雜的過程依照功能提取成各個小方法,這樣就能夠使代碼的可讀性、維護性得到提高。
正文:例如以下代碼所看到的,CalculateGrandTotal方法里面包括了多個邏輯,第一計算subTotal的總和。第二subTotal 要循環減去discount,也就是計算Discounts。第三就是計算Tax。
所以我們能夠依據功能把他們拆分成三個小方法。
using System.Collections.Generic;
namespace LosTechies.DaysOfRefactoring.ExtractMethod.Before
{
public class Receipt
{
private IList<decimal>Discounts { get; set;}
private IList<decimal>ItemTotals { get; set;}
public decimalCalculateGrandTotal()
{
decimal subTotal= 0m;
foreach (decimal itemTotal in ItemTotals)
subTotal += itemTotal;
if (Discounts.Count> 0)
{
foreach(decimal discount in Discounts)
subTotal -= discount;
}
decimal tax= subTotal * 0.065m;
subTotal += tax;
return subTotal;
}
}
}
重構后的代碼例如以下。然后CalculateGrandTotal方法就直接調用CalculateSubTotal、CalculateDiscounts、CalculateTax,從而是整個邏輯看起來更加清晰,而且可讀性和維護性也得到了大大提高。
using System.Collections.Generic;
namespace LosTechies.DaysOfRefactoring.ExtractMethod.After
{
public class Receipt
{
private IList<decimal>Discounts { get; set;}
private IList<decimal>ItemTotals { get; set;}
public decimalCalculateGrandTotal()
{
decimal subTotal= CalculateSubTotal();
subTotal =CalculateDiscounts(subTotal);
subTotal = CalculateTax(subTotal);
return subTotal;
}
privatedecimal CalculateTax(decimal subTotal)
{
decimal tax= subTotal * 0.065m;
subTotal += tax;
return subTotal;
}
privatedecimal CalculateDiscounts(decimal subTotal)
{
if (Discounts.Count> 0)
{
foreach(decimal discount in Discounts)
subTotal -= discount;
}
return subTotal;
}
privatedecimal CalculateSubTotal()
{
decimal subTotal= 0m;
foreach (decimal itemTotal in ItemTotals)
subTotal += itemTotal;
return subTotal;
}
}
}
總結:這個重構在非常多公司都有一些的代碼規范作為參考。比方一個類不能超過多少行代碼。一個方法里面不能超過多少行代碼。這在一定程度上也能使程序猿把這些復雜的邏輯剝離成意義非常清楚的小方法。
11、使用策略類
概念:本文中的“使用策略類” 是指用設計模式中的策略模式來替換原來的switch case和if else語句,這樣能夠解開耦合。同一時候也使維護性和系統的可擴展性大大增強。
正文:如以下代碼所看到的,ClientCode 類會更加枚舉State的值來調用ShippingInfo 的不同方法。可是這樣就會產生非常多的推斷語句。假設代碼量加大,類變得非常大了的話,維護中修改也會變得非常大,每次修改一個地方。都要對整個結構進行編譯(假如是多個project)。所以我們想到了對它進行重構,剝開耦合。
namespace LosTechies.DaysOfRefactoring.SwitchToStrategy.Before
{
public class ClientCode
{
public decimalCalculateShipping()
{
ShippingInfoshippingInfo = new ShippingInfo();
return shippingInfo.CalculateShippingAmount(State.Alaska);
}
}
public enum State
{
Alaska,
NewYork,
Florida
}
public class ShippingInfo
{
public decimalCalculateShippingAmount(State shipToState)
{
switch (shipToState)
{
case State.Alaska:
returnGetAlaskaShippingAmount();
case State.NewYork:
returnGetNewYorkShippingAmount();
case State.Florida:
return GetFloridaShippingAmount();
default:
return0m;
}
}
privatedecimal GetAlaskaShippingAmount()
{
return 15m;
}
privatedecimal GetNewYorkShippingAmount()
{
return 10m;
}
privatedecimal GetFloridaShippingAmount()
{
return 3m;
}
}
}
重構后的代碼例如以下所看到的,抽象出一個IShippingCalculation 接口,然后把ShippingInfo 類里面的GetAlaskaShippingAmount、GetNewYorkShippingAmount、GetFloridaShippingAmount三個方法分別提煉成三個類,然后繼承自IShippingCalculation 接口,這樣在調用的時候就能夠通過IEnumerable<IShippingCalculation> 來解除之前的switch case語句,這和IOC的做法頗為相似。
using System;
using System.Collections.Generic;
using System.Linq;
namespace LosTechies.DaysOfRefactoring.SwitchToStrategy.After_WithIoC
{
public interface IShippingInfo
{
decimal CalculateShippingAmount(State state);
}
public class ClientCode
{
[Inject]
public IShippingInfo ShippingInfo { get; set; }
public decimalCalculateShipping()
{
return ShippingInfo.CalculateShippingAmount(State.Alaska);
}
}
public enum State
{
Alaska,
NewYork,
Florida
}
public class ShippingInfo : IShippingInfo
{
private IDictionary<State,IShippingCalculation>ShippingCalculations { get; set; }
public ShippingInfo(IEnumerable<IShippingCalculation>shippingCalculations)
{
ShippingCalculations = shippingCalculations.ToDictionary(calc=> calc.State);
}
public decimalCalculateShippingAmount(State shipToState)
{
return ShippingCalculations[shipToState].Calculate();
}
}
public interface IShippingCalculation
{
State State{ get; }
decimal Calculate();
}
public class AlaskShippingCalculation : IShippingCalculation
{
public State State { get {return State.Alaska;} }
public decimalCalculate()
{
return 15m;
}
}
public class NewYorkShippingCalculation : IShippingCalculation
{
public State State { get {return State.NewYork;} }
public decimalCalculate()
{
return 10m;
}
}
public class FloridaShippingCalculation: IShippingCalculation
{
public State State { get {return State.Florida;} }
public decimalCalculate()
{
return 3m;
}
}
}
總結:這樣的重構在設計模式其中把它單獨取了一個名字——策略模式,這樣做的優點就是能夠隔開耦合,以注入的形式實現功能,這使添加功能變得更加easy和簡便,相同也增強了整個系統的穩定性和健壯性。
12、分解依賴
概念:本文中的“分解依賴” 是指對部分不滿足我們要求的類和方法進行依賴分解,通過裝飾器來達到我們須要的功能。
正文:正如以下代碼所看到的,假設你要在你的代碼中增加單元測試但有一部分代碼是你不想測試的,那么你應用使用這個的重構。以下的樣例中我們應用靜態類來完畢某些工作。但問題是在單元測試時我們無法mock靜態類,所以我們僅僅能引入靜態類的裝飾接口來分解對靜態類的依賴。從而我們使我們的調用類僅僅須要依賴於裝飾接口就能完畢這個操作。
namespace LosTechies.DaysOfRefactoring.BreakDependencies.Before
{
public classAnimalFeedingService
{
privatebool FoodBowlEmpty { get; set; }
publicvoid Feed()
{
if (FoodBowlEmpty)
Feeder.ReplenishFood();
//more code to feed the animal
}
}
publicstatic class Feeder
{
publicstatic void ReplenishFood()
{
//fill up bowl
}
}
}
重構后代碼例如以下,我們加入一個接口和一個實現類,在實現類中調用靜態類的方法,所以說詳細做什么事情沒有改變,改變的僅僅是形式,但這樣做的一個優點是添加了了代碼的可測試性。在應用了分解依賴模式后,我們就能夠在單元測試的時候mock一個IFeederService對象並通過AnimalFeedingService的構造函數傳遞給它。這樣就能夠完畢我們須要的功能。
namespace LosTechies.DaysOfRefactoring.BreakDependencies.After
{
public classAnimalFeedingService
{
public IFeederService FeederService { get; set; }
public AnimalFeedingService(IFeederService feederService)
{
FeederService =feederService;
}
privatebool FoodBowlEmpty { get; set; }
public void Feed()
{
if (FoodBowlEmpty)
FeederService.ReplenishFood();
//more code to feed the animal
}
}
publicinterface IFeederService
{
void ReplenishFood();
}
public classFeederService : IFeederService
{
publicvoid ReplenishFood()
{
Feeder.ReplenishFood();
}
}
publicstatic class Feeder
{
publicstatic void ReplenishFood()
{
//fill up bowl
}
}
}
總結:這個重構在非常多時候和設計模式中的一些思想類似,使用中間的裝飾接口來分解兩個類之間的依賴,對類進行裝飾。然后使它滿足我們所須要的功能。
13、提取方法對象
概念:本文中的“提取方法對象”是指當你發現一個方法中存在過多的局部變量時,你能夠通過使用“提取方法對象”重構來引入一些方法,每一個方法完畢任務的一個步驟,這樣能夠使得程序變得更具有可讀性。
正文:例如以下代碼所看到的,Order 類中的Calculate方法要完畢非常多功能,在之前我們用“提取方法”來進行重構,如今我們採取“提取方法對象”來完畢重構。
using System.Collections.Generic;
namespace LosTechies.DaysOfRefactoring.ExtractMethodObject.Before
{
public class OrderLineItem
{
public decimal Price { get; private set; }
}
public class Order
{
private IList<OrderLineItem> OrderLineItems { get; set; }
private IList<decimal> Discounts { get;set; }
private decimal Tax { get; set; }
public decimal Calculate()
{
decimal subTotal = 0m;
// Total up line items
foreach(OrderLineItem lineItemin OrderLineItems)
{
subTotal += lineItem.Price;
}
// Subtract Discounts
foreach (decimaldiscount in Discounts)
subTotal -= discount;
// CalculateTax
decimal tax = subTotal * Tax;
// Calculate GrandTotal
decimal grandTotal = subTotal + tax;
return grandTotal;
}
}
}
正例如以下代碼所看到的。我們引入了OrderCalculator類,該類實現了全部的計算方法。Order類將自身傳遞給 OrderCalculator類並調用Calculate方法完畢計算過程。
using System.Collections.Generic;
namespace LosTechies.DaysOfRefactoring.ExtractMethodObject.After
{
public class OrderLineItem
{
public decimal Price { get; private set; }
}
public class Order
{
public IEnumerable<OrderLineItem> OrderLineItems { get; private set; }
public IEnumerable<decimal> Discounts { get;private set; }
public decimal Tax { get; private set; }
public decimal Calculate()
{
return new OrderCalculator(this).Calculate();
}
}
public class OrderCalculator
{
private decimal SubTotal { get; set; }
private IEnumerable<OrderLineItem> OrderLineItems { get; set; }
private IEnumerable<decimal> Discounts { get;set; }
private decimal Tax { get; set; }
public OrderCalculator(Order order)
{
OrderLineItems = order.OrderLineItems;
Discounts = order.Discounts;
Tax = order.Tax;
}
public decimal Calculate()
{
CalculateSubTotal();
SubtractDiscounts();
CalculateTax();
return SubTotal;
}
private void CalculateSubTotal()
{
// Total up line items
foreach (OrderLineItemlineItem in OrderLineItems)
SubTotal += lineItem.Price;
}
private void SubtractDiscounts()
{
// Subtract Discounts
foreach (decimaldiscount in Discounts)
SubTotal -= discount;
}
private void CalculateTax()
{
// Calculate Tax
SubTotal +=SubTotal * Tax;
}
}
}
總結:本文的重構方法在有的時候還是比較實用,但這樣會造成字段的添加。同一時候也會帶來一些維護的不便,它和“提取方法”最大的差別就是一個通過方法返回須要的數據,還有一個則是通過字段來存儲方法的結果值,所以在非常大程度上我們都會選擇“提取方法”。
14、分離職責
概念:本文中的“分離職責”是指當一個類有很多職責時。將部分職責分離到獨立的類中,這樣也符合面向對象的五大特征之中的一個的單一職責原則。同一時候也能夠使代碼的結構更加清晰,維護性更高。
正文:例如以下代碼所看到的,Video類有兩個職責。一個是處理video rental,還有一個是計算每一個客戶的總租金。我們能夠將這兩個職責分離出來,由於計算每一個客戶的總租金能夠在Customer計算。這也比較符合常理。
using System.Collections.Generic;
using System.Linq;
namespace LosTechies.DaysOfRefactoring.BreakResponsibilities.Before
{
public class Video
{
public voidPayFee(decimal fee)
{
}
public voidRentVideo(Video video, Customer customer)
{
customer.Videos.Add(video);
}
publicdecimal CalculateBalance(Customer customer)
{
returncustomer.LateFees.Sum();
}
}
public class Customer
{
public IList<decimal>LateFees { get; set;}
public IList<Video>Videos { get; set;}
}
}
重構后的代碼例如以下,這樣Video 的職責就變得非常清晰,同一時候也使代碼維護性更好。
using System.Collections.Generic;
using System.Linq;
namespace LosTechies.DaysOfRefactoring.BreakResponsibilities.After
{
public class Video
{
public voidRentVideo(Video video, Customer customer)
{
customer.Videos.Add(video);
}
}
public class Customer
{
public IList<decimal>LateFees { get; set;}
public IList<Video>Videos { get; set;}
public voidPayFee(decimal fee)
{
}
publicdecimal CalculateBalance(Customer customer)
{
return customer.LateFees.Sum();
}
}
}
總結:這個重構常常會用到。它和之前的“移動方法”有幾分相似之處,讓方法放在合適的類中。而且簡化類的職責,同一時候這也是面向對象五大原則之中的一個和設計模式中的重要思想。
15、移除反復內容
概念:本文中的“移除反復內容”是指把一些非常多地方都用到的邏輯提煉出來,然后提供給調用者統一調用。
正文:例如以下代碼所看到的,ArchiveRecord和CloseRecord都會用到Archived = true; 和DateArchived = DateTime.Now; 這兩條語句,所以我們就能夠對它進行重構。
using System;
namespace LosTechies.DaysOfRefactoring.RemoveDuplication.Before
{
public class MedicalRecord
{
public DateTimeDateArchived { get; private set; }
public bool Archived { get; private set; }
public void ArchiveRecord()
{
Archived = true;
DateArchived = DateTime.Now;
}
public void CloseRecord()
{
Archived = true;
DateArchived = DateTime.Now;
}
}
}
重構后的代碼例如以下所看到的,我們提煉了SwitchToArchived方法來封裝公用的操作。然后給ArchiveRecord和CloseRecord統一調用。
using System;
namespace LosTechies.DaysOfRefactoring.RemoveDuplication.After
{
public class MedicalRecord
{
public DateTimeDateArchived { get; private set; }
public bool Archived{ get; private set;}
public void ArchiveRecord()
{
SwitchToArchived();
}
public void CloseRecord()
{
SwitchToArchived();
}
private void SwitchToArchived()
{
Archived = true;
DateArchived = DateTime.Now;
}
}
}
總結:這個重構非常easy,絕大多數程序猿都會使用這樣的重構方法,但有時因為習慣、時間、趕進度等原因而忽略它,所以會使得整個系統雜亂無章,到處都是Ctrl+C和Ctrl+V的痕跡。
16、封裝條件
概念:本文中的“封裝條件”是指條件關系比較復雜時,代碼的可讀性會比較差,所以這時我們應當依據條件表達式是否須要參數將條件表達式提取成可讀性更好的屬性或者方法,假設條件表達式不須要參數則能夠提取成屬性,假設條件表達式須要參數則能夠提取成方法。
正文:例如以下代碼所看到的,PerformCoolFunction里面的if條件推斷比較復雜。看起來有點雜亂,所以就把它提出來。
using System;
namespace LosTechies.DaysOfRefactoring.EncapsulateConditional.Before
{
public classRemoteControl
{
privatestring[] Functions { get; set; }
privatestring Name { get; set; }
privateint CreatedYear { get; set; }
publicstring PerformCoolFunction(string buttonPressed)
{
//Determine if we are controlling some extra function
// that requires specialconditions
if (Functions.Length > 1 && Name == "RCA" && CreatedYear > DateTime.Now.Year - 2)
return"doSomething";
}
}
}
例如以下代碼所看到的。我們把條件表達式封裝成HasExtraFunctions屬性,這樣先前的條件推斷就成了if (HasExtraFunctions) 。所以這樣就在非常大程度上提高了可讀性。
using System;
namespace LosTechies.DaysOfRefactoring.EncapsulateConditional.After
{
public classRemoteControl
{
privatestring[] Functions { get; set; }
privatestring Name { get; set; }
privateint CreatedYear { get; set; }
privatebool HasExtraFunctions
{
get {return Functions.Length > 1 && Name== "RCA" && CreatedYear> DateTime.Now.Year - 2; }
}
publicstring PerformCoolFunction(string buttonPressed)
{
//Determine if we are controlling some extra function
// that requires specialconditions
if (HasExtraFunctions)
return"doSomething";
}
}
}
總結:這個重構在非常大程度上能改善代碼的可讀性,尤其是在一個邏輯非常復雜的應用中。把這些條件推斷封裝成一個有意義的名字,這樣非常復雜的邏輯也會立馬變得簡單起來。
17、提取父類
正文:Dog 類中的EatFood和Groom有可能被其它類用到,由於他們都是動物的一些公有性質,所以這個時候我們就會考慮對它進行提煉。
namespace LosTechies.DaysOfRefactoring.ExtractSuperclass.Before
{
public classDog
{
publicvoid EatFood()
{
//eat some food
}
public void Groom()
{
//perform grooming
}
}
}
代碼例如以下所看到的。提取了Animal 方法來封裝公用的EatFood和Groom類,從而使其它繼承了Animal 類的子類都能夠使用這兩個方法了。
namespace LosTechies.DaysOfRefactoring.ExtractSuperclass.After
{
public classAnimal
{
publicvoid EatFood()
{
//eat some food
}
publicvoid Groom()
{
//perform grooming
}
}
public classDog : Animal
{
}
}
總結:這個重構是典型的繼承使用方法,非常多程序猿都會選擇這樣做,可是要注意正確的使用,不要造成過度使用了繼承,假設過度使用了,請考慮用接口、組合和聚合來實現。
18、使用條件推斷取代異常
概念:本文中的“使用條件推斷取代異常”是指把沒有必要使用異常做推斷的條件盡量改為條件推斷。
正文:例如以下代碼所看到的,在日常的編碼中我們常常須要用到異常來控制程序流。Start方法里面用try catch 做條件推斷,我們知道這里沒有必要使用這樣的方式,由於你不須要做類型不可控的類型轉換,也不須要處理異常行為,所以我們應該對它進行重構。
namespace LosTechies.DaysOfRefactoring.ReplaceException.Before
{
public classMicrowave
{
private IMicrowaveMotorMotor { get; set;}
publicbool Start(object food)
{
boolfoodCooked = false;
try
{
Motor.Cook(food);
foodCooked = true;
}
catch(InUseException)
{
foodcooked = false;
}
return foodCooked;
}
}
}
重構后的代碼例如以下所看到的,try catch做條件推斷的語句改成了if return的方式,這樣在非常多程度上統一了代碼的書寫,同一時候也提高了性能。
namespace LosTechies.DaysOfRefactoring.ReplaceException.After
{
public classMicrowave
{
private IMicrowaveMotorMotor { get; set;}
publicbool Start(object food)
{
if (Motor.IsInUse)
returnfalse;
Motor.Cook(food);
returntrue;
}
}
}
總結: 這個重構在項目代碼中也經經常使用到。由於對於一部分程序猿,是非常難把握什么時候用try catch ,什么地方該用try catch。記得之前大家還專門討論過這些,比方怎樣用好以及在大中型項目中應該把它放在哪一個組件中等。
19、提取工廠類
概念:本文中的“提取工廠類”是指假設要創建的對象非常多,則代碼會變的非常復雜。一種非常好的方法就是提取工廠類。
正文:一般來說我們須要在代碼中設置一些對象,以便獲得它們的狀態,從而使用對象,所謂的設置通常來說就是創建對象的實例並調用對象的方法。有時假設要創建的對象非常多,則代碼會變的非常復雜。這便是工廠模式發揮作用的情形。工廠模式的復雜應用是使用抽象工廠創建對象集,但我們在這里僅僅是使用主要的工廠類創建對象的一個簡單應用。
例如以下代碼所看到的。New方法包括創建類的整個邏輯,假設如今要創建的類比較多並且邏輯比較復雜的話(如依據不同條件創建對象,什么時候創建對象),我們的New方法邏輯會變得非常大,同一時候代碼也變得非常難維護。所以我們就會採用提取工廠類的方式進行提煉。
namespace LosTechies.DaysOfRefactoring.ExtractServiceClass.Before
{
public class PoliceCarController
{
public PoliceCarNew(int mileage, bool serviceRequired)
{
PoliceCarpoliceCar = new PoliceCar();
policeCar.ServiceRequired = serviceRequired;
policeCar.Mileage = mileage;
return policeCar;
}
}
}
那么重構后的代碼例如以下,New方法變得非常easy了,指須要調用實現接IPoliceCarFactory 接口的PoliceCarFactory 類就能夠返回對象。這樣就隔開了創建對象的邏輯,假設需求如今變為依據不同的條件創建不同的對象,什么時候創建對象等都變成了比較簡單的事情。在后期能夠把對象都配置在XML里面,使用反射的方式實現IOC注入創建。
namespace LosTechies.DaysOfRefactoring.ExtractServiceClass.After
{
publicinterface IPoliceCarFactory
{
PoliceCar Create(int mileage, bool serviceRequired);
}
public classPoliceCarFactory : IPoliceCarFactory
{
public PoliceCarCreate(int mileage, boolserviceRequired)
{
PoliceCar policeCar = new PoliceCar();
policeCar.ReadForService =serviceRequired;
policeCar.Mileage = mileage;
returnpoliceCar;
}
}
public classPoliceCarController
{
public IPoliceCarFactory PoliceCarFactory { get; set; }
public PoliceCarController(IPoliceCarFactory policeCarFactory)
{
PoliceCarFactory =policeCarFactory;
}
public PoliceCarNew(int mileage, bool serviceRequired)
{
returnPoliceCarFactory.Create(mileage, serviceRequired);
}
}
}
總結:這個重構常常會在項目中使用。假設要創建的對象是一個,你能夠採用簡單工廠。可是這樣的方式還是會存在非常多依賴,維護起來也比較不方便。
所以推薦使用工廠方法模式,把實例化延遲到子類。假設你要創建一系列的對象。那么就推薦你使用抽象工廠模式。可是要注意不要過度設計,僅僅要能滿足不斷變化的需求和給以后的維護和重構帶來方便就可以。
20、提取子類
概念:本文中的”提取子類”是指把基類中的一些不是全部子類都須要訪問的方法調整到子類中。
正文:當你的基類中存在一些方法不是全部的子類都須要訪問,你想將它們調整到子類中時。這個重構會變得非常實用了。
例如以下代碼所看到的。我們須要一個 Registration類用來處理學生選課的信息。可是當Registration類開始工作后,我們意識到我們會在兩種不同的上下文中使用 Registration類,NonRegistrationAction和Notes僅僅有在我們處理未注冊情況下才用到。
所以我們將NonRegistration和Notes提到單獨的NonRegistration類中。
using System;
namespace LosTechies.DaysOfRefactoring.SampleCode.ExtractSubclass.Before
{
public classRegistration
{
public NonRegistrationActionAction { get; set;}
publicdecimal RegistrationTotal { get; set; }
publicstring Notes { get; set; }
publicstring Description { get; set; }
public DateTime RegistrationDate { get; set; }
}
}
重構后的代碼例如以下所看到的。這樣也滿足面向對象五大原則之中的一個的單一職責。
同一時候也讓類的結構變得更加清晰,增強了可維護性。
using System;
namespace LosTechies.DaysOfRefactoring.SampleCode.ExtractSubclass.After
{
public classRegistration
{
publicdecimal RegistrationTotal { get; set; }
publicstring Description { get; set; }
public DateTime RegistrationDate { get; set; }
}
public classNonRegistration : Registration
{
public NonRegistrationActionAction { get; set;}
publicstring Notes { get; set; }
}
}
總結:這個重構方法經經常使用來規范類的職責。和之前的一些重構方法也有些類似。
21、合並繼承
概念:本文中的”合並繼承”是指假設子類的屬性和方法也適合於基類。那么就能夠移除子類,從而降低依賴關系。
正文:上一篇我們講到“提取子類”重構是指當基類中的一個責任不被全部的子類所須要時。將這些責任提取到合適的子類中。而我們今天所要講的的“合並繼承”重構一般用在當我們認為不須要子類的時候。
例如以下代碼所看到的,StudentWebSite子類除了有一個屬性用來說明站點是否是活動的外沒有別的責任,在這樣的情形下我們意識到IsActive屬性能夠應用到全部的站點,所以我們能夠將IsActive屬性上移到基類中。並去掉StudentWebSite類。
using System.Collections.Generic;
namespace LosTechies.DaysOfRefactoring.SampleCode.CollapseHierarchy.Before
{
public class Website
{
public string Title { get; set; }
public string Description { get; set; }
public IEnumerable<Webpage>Pages { get; set;}
}
public class StudentWebsite: Website
{
public bool IsActive { get; set; }
}
}
重構后的代碼例如以下:
using System.Collections.Generic;
namespace LosTechies.DaysOfRefactoring.SampleCode.CollapseHierarchy.After
{
public class Website
{
public string Title { get; set; }
public string Description { get; set; }
public IEnumerable<Webpage>Pages { get; set;}
public bool IsActive { get; set; }
}
}
總結: 這篇和上篇事實上最主要論述了子類和父類的繼承關系以及怎樣推斷什么時候須要使用繼承,一般我們都能處理好這些關系。所以相對照較簡單。
22、分解方法
概念:本文中的”分解方法”是指把我們所做的這個功能不停的分解方法,直到將一個慷慨法分解為名字有意義且可讀性更好的若干個小方法。
正文:例如以下代碼所看到的。由於現實中AcceptPayment方法不會做這么多的事情。。所以我們通過幾次分解將 AcceptPayment拆分成若干個名字有意義且可讀性更好的小方法。
using System.Collections.Generic;
using System.Linq;
namespace LosTechies.DaysOfRefactoring.SampleCode.BreakMethod.Before
{
public classCashRegister
{
public CashRegister()
{
Tax = 0.06m;
}
privatedecimal Tax { get; set; }
public void AcceptPayment(Customer customer, IEnumerable<Product> products, decimalpayment)
{
decimalsubTotal = 0m;
foreach(Product product in products)
{
subTotal +=product.Price;
}
foreach(Product product in products)
{
subTotal -=product.AvailableDiscounts;
}
decimalgrandTotal = subTotal * Tax;
customer.DeductFromAccountBalance(grandTotal);
}
}
public classCustomer
{
publicvoid DeductFromAccountBalance(decimal amount)
{
//deduct from balance
}
}
public classProduct
{
publicdecimal Price { get; set; }
publicdecimal AvailableDiscounts { get; set; }
}
}
重構后的代碼例如以下,我們把AcceptPayment的內部邏輯拆分成了CalculateSubtotal、SubtractDiscounts、AddTax、SubtractFromCustomerBalance四個功能明白且可讀性更好的小方法。
using System.Collections.Generic;
namespace LosTechies.DaysOfRefactoring.SampleCode.BreakMethod.After
{
public classCashRegister
{
public CashRegister()
{
Tax = 0.06m;
}
privatedecimal Tax { get; set; }
private IEnumerable<Product>Products { get; set;}
publicvoid AcceptPayment(Customer customer,IEnumerable<Product>products, decimal payment)
{
decimalsubTotal = CalculateSubtotal();
subTotal =SubtractDiscounts(subTotal);
decimalgrandTotal = AddTax(subTotal);
SubtractFromCustomerBalance(customer,grandTotal);
}
privatevoid SubtractFromCustomerBalance(Customer customer,decimal grandTotal)
{
customer.DeductFromAccountBalance(grandTotal);
}
privatedecimal AddTax(decimal subTotal)
{
returnsubTotal * Tax;
}
privatedecimal SubtractDiscounts(decimal subTotal)
{
foreach(Product product in Products)
{
subTotal -= product.AvailableDiscounts;
}
returnsubTotal;
}
privatedecimal CalculateSubtotal()
{
decimalsubTotal = 0m;
foreach(Product product in Products)
{
subTotal += product.Price;
}
returnsubTotal;
}
}
public classCustomer
{
publicvoid DeductFromAccountBalance(decimal amount)
{
//deduct from balance
}
}
public classProduct
{
publicdecimal Price { get; set; }
publicdecimal AvailableDiscounts { get; set; }
}
}
總結:事實上這個重構和我們前面講的“提取方法”和“提取方法對象”如出一轍,尤其是“提取方法”,所以大家僅僅要知道用這樣的思想重構即可。
23、引入參數對象
概念:本文中的“引入參數對象”是指當一個方法的參數過多或者過為復雜時,能夠考慮把這些參數封裝成一個單獨的類。
正文:假設一個方法所須要的參數大於5個,理解該方法的簽名就變得比較困難,由於這樣感覺參數非常長、樣式不好而且沒有分類,所以我們有必要把參數進行封裝。
namespace LosTechies.DaysOfRefactoring.SampleCode.ParameterObject.Before
{
public classRegistration
{
publicvoid Create(decimal amount, Studentstudent, IEnumerable<Course> courses, decimal credits)
{
//do work
}
}
}
通常這樣的情形下創建一個用戶傳遞參數的類是非常有幫助的,這會使得代碼更easy明確也更靈活,由於當你須要添加參數時,僅僅須要給參數類加入一個屬性就可以。
請注意僅僅有當你發現方法的參數比較多時才應該應用該重構。假設方法的參數比較少,就沒有必要應用此重構,由於該重構會添加系統中類的數量,同一時候也會加大維護負擔。
所以要看參數情況而定。
重構后的代碼例如以下:
using System.Collections.Generic;
namespace LosTechies.DaysOfRefactoring.SampleCode.ParameterObject.After
{
public classRegistrationContext
{
publicdecimal Amount { get; set; }
public StudentStudent { get; set;}
public IEnumerable<Course> Courses { get; set; }
publicdecimal Credits { get; set; }
}
public classRegistration
{
publicvoid Create(RegistrationContext registrationContext)
{
//do work
}
}
}
總結:這樣的重構非常重要,尤其是當一個方法的參數比較多的時候,無論是大中型項目還是小型項目,都會遇到這樣的場景。所以建議大家多使用這個重構。這樣的封裝的思想在SOA 里面也常常運用到,封裝輸入Message。封裝輸出Message。消息來和消息去以及消息間的交互就構成了整個應用體系。
24、分解復雜推斷
概念:本文中的”分解復雜推斷”是指把原來復雜的條件推斷等語句用盡快返回等方式簡化代碼。
正文:簡單的來說,當你的代碼中有非常深的嵌套條件時,花括號就會在代碼中形成一個長長的箭頭。
我們常常在不同的代碼中看到這樣的情況,而且這樣的情況也會擾亂代碼的可讀性。
例如以下代碼所看到的。HasAccess方法里面包括一些嵌套條件,假設再加一些條件或者添加復雜度,那么代碼就非常可能出現幾個問題:1。可讀性差。 2,非常easy出現異常。
3。性能較差。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace LosTechies.DaysOfRefactoring.SampleCode.ArrowheadAntipattern.Before
{
public classSecurity
{
public ISecurityCheckerSecurityChecker { get; set;}
public Security(ISecurityCheckersecurityChecker)
{
SecurityChecker =securityChecker;
}
publicbool HasAccess(User user, Permission permission, IEnumerable<Permission> exemptions)
{
boolhasPermission = false;
if (user!= null)
{
if(permission != null)
{
if (exemptions.Count() == 0)
{
if (SecurityChecker.CheckPermission(user, permission)|| exemptions.Contains(permission))
{
hasPermission= true;
}
}
}
}
returnhasPermission;
}
}
}
那么重構上面的代碼也非常easy,假設有可能的話,盡量將條件從方法中移除,我們讓代碼在做處理任務之前先檢查條件。假設條件不滿足就盡快返回。不繼續運行。以下是重構后的代碼:
using System.Collections.Generic;
using System.Linq;
namespace LosTechies.DaysOfRefactoring.SampleCode.ArrowheadAntipattern.After
{
public classSecurity
{
public ISecurityCheckerSecurityChecker { get; set;}
public Security(ISecurityCheckersecurityChecker)
{
SecurityChecker = securityChecker;
}
publicbool HasAccess(User user, Permission permission, IEnumerable<Permission> exemptions)
{
if (user== null || permission == null)
returnfalse;
if (exemptions.Contains(permission))
returntrue;
returnSecurityChecker.CheckPermission(user, permission);
}
}
}
總結:這個重構非常重要,它和后面講的”盡快返回“有些類似。我們在做復雜的處理過程時,要常常考慮這個重構,用好了它,會對我們的幫助非常大。
25、引入契約式設計
概念:本文中的”引入契約式設計”是指我們應該相應該對輸入和輸出進行驗證,以確保系統不會出現我們所想象不到的異常和得不到我們想要的結果。
正文:契約式設計規定方法應該對輸入和輸出進行驗證,這樣你便能夠保證你得到的數據是能夠工作的,一切都是按預期進行的。假設不是按預期進行,異常或是錯誤就應該被返回。以下我們舉的樣例中。我們方法中的參數可能會值為null的情況。在這樣的情況下因為我們沒有驗證,NullReferenceException異常會報出。另外在方法的結尾處我們也沒有保證會返回一個正確的decimal值給調用方法的對象。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace LosTechies.DaysOfRefactoring.SampleCode.Day25_DesignByContract
{
public class CashRegister
{
public decimal TotalOrder(IEnumerable<Product> products, Customercustomer)
{
decimal orderTotal =products.Sum(product => product.Price);
customer.Balance += orderTotal;
return orderTotal;
}
}
}
對上面的代碼重構是非常easy的,首先我們處理不會有一個null值的customer對象。檢查我們最少會有一個product對象。在返回訂單總和 之前先確保我們會返回一個有意義的值。假設上面說的檢查有不論什么一個失敗,我們就拋出相應的異常,並在異常里說明錯誤的具體信息,而不是直接拋出 NullReferenceException。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Contracts;
namespace LosTechies.DaysOfRefactoring.SampleCode.DesignByContract.After
{
public class CashRegister
{
public decimal TotalOrder(IEnumerable<Product> products, Customercustomer)
{
if (customer == null)
thrownew ArgumentNullException("customer", "Customercannot be null");
if (products.Count()== 0)
thrownew ArgumentException("Must have at least one product to total","products");
decimal orderTotal =products.Sum(product => product.Price);
customer.Balance += orderTotal;
if (orderTotal== 0)
thrownew ArgumentOutOfRangeException("orderTotal", "OrderTotal should not be zero");
return orderTotal;
}
}
}
上面的代碼中加入了額外的代碼來進行驗證。盡管看起來代碼復雜度添加了,但我覺得這是非常值得做的,由於當NullReferenceException發生時去追查異常的具體信息真是非常令人討厭的事情。
總結:微軟在處理代碼乃至產品的時候,非常喜歡應用此重構,你假設認真看它的代碼庫,認真看一下WCF的設計,就不難發現了。這個重構建議大家常常使用。這會增強整個系統的穩定性和健壯性。
26、避免雙重否定
概念:本文中的”避免雙重否定”是指把代碼中的雙重否定語句改動成簡單的肯定語句,這樣即讓代碼可讀,同一時候也給維護帶來了方便。
正文:避免雙重否定重構本身很easy實現,但我們卻在太多的代碼中見過由於雙重否定減少了代碼的可讀性以致於很讓人easy誤解真正意圖。存在雙重否定的代碼具有很大的危害性,由於這樣的類型的代碼easy引起錯誤的如果。錯誤的如果又會導致書寫出錯誤的維護代碼,終於會導致bug產生。
具 體能夠看以下的代碼:
using System.Collections.Generic;
using LosTechies.DaysOfRefactoring.SampleCode.BreakMethod.After;
namespace LosTechies.DaysOfRefactoring.SampleCode.DoubleNegative.Before
{
publicclass Order
{
publicvoid Checkout(IEnumerable<Product> products, Customer customer)
{
if (!customer.IsNotFlagged)
{
//the customer account is flagged
// log some errors andreturn
return;
}
//normal order processing
}
}
publicclass Customer
{
publicdecimal Balance { get; private set; }
publicbool IsNotFlagged
{
get { return Balance < 30m; }
}
}
}
如上代碼中的雙重否定可讀性很低,由於我們很難搞明確雙重否定的正確值。要重構它也很easy,例如以下是重構后的代碼:
using System.Collections.Generic;
using LosTechies.DaysOfRefactoring.SampleCode.BreakMethod.After;
namespace LosTechies.DaysOfRefactoring.SampleCode.DoubleNegative.After
{
public classOrder
{
publicvoid Checkout(IEnumerable<Product>products, Customer customer)
{
if (customer.IsFlagged)
{
//the customer account is flagged
// log some errors andreturn
return;
}
//normal order processing
}
}
public classCustomer
{
publicdecimal Balance { get; private set; }
publicbool IsFlagged
{
get { return Balance >= 30m; }
}
}
}
總結: ”雙重否定“非常easy讓人產生錯誤的推斷,也非常難讓人理解你的代碼,所以這個重構在我們的代碼中是非常重要的,尤其是在推斷條件非常多且業務復雜的時候。
27、去除上帝類
概念:本文中的”去除上帝類”是指把一個看似功能非常強且非常難維護的類。依照職責把自己的屬性或方法分派到各自的類中或分解成功能明白的類。從而去掉上帝類。
正文:我們常常能夠在一些原來的代碼中見到一些類明白違反了SRP原則(單一原則),這些類通常以“Utils”或“Manager”后綴 結尾,但有時這些類也沒有這些特征。它不過多個類多個方法的組合。還有一個關於上帝類的特征是通常這些類中的方法被用凝視分隔為不同的分組。
那么久而久之。這些類被轉換為那些沒有人願意進行歸並到合適類的方法的聚集地,對這些類進行重構是將類中的代碼依照職責分派到各自的類中,這樣就解除了上帝類,也減輕了維護的負擔。
| using System.Collections.Generic;
|
我們看到要重構上面的代碼是非常easy的,僅僅要將相關的方法按職責分派到相應的類中就可以,帶來的優點就是這會降低代碼的顆粒度並降低未來維護代碼的成本。以下是重構后的代碼。它將上面
的代碼依照職責分為了兩個不同的類。
| using System.Collections.Generic;
|
總結: ”去除上帝類“是我們常常easy造成的,第一是由於簡便,看到有一個現成的類,大家都會喜歡把代碼往里面寫,最后導致越寫越大。而且聲明功能都有,這樣即減少了可讀性,也造成了維護的負擔。
28、為布爾方法命名
概念:本文中的”為布爾方法命名”是指假設一個方法帶有大量的bool 參數時,能夠依據bool 參數的數量,提取出若干個獨立的方法來簡化參數。
正文:我們如今要說的重構並非普通字面意義上的重構。它有非常多值得討論的地方。
當一個方法帶有大量的bool 參數時,會導致方法非常easy被誤解並產生非預期的行為,
依據布爾型參數的數量,我們能夠決定提取出若干個獨立的方法來。
詳細代碼例如以下:
| using LosTechies.DaysOfRefactoring.BreakResponsibilities.After;
|
我們能夠將上面的bool參數以獨立方法的形式暴露給調用端以提高代碼的可讀性,同一時候我們還須要將原來的方法改為private以限制其可訪問性。顯然我們關於要
提取的獨立方法會有一個非常大的排列組合,這是一大缺點,所以我們能夠考慮引入”參數對象“重構。
| using LosTechies.DaysOfRefactoring.BreakResponsibilities.After;
|
總結: ”為布爾方法命名“這個重構在非常多時候都不經常使用,假設用戶的參數可枚舉,我們通常會枚舉它的值。只是使用這樣的重構也有優點,就是分解開來以后,方法多了。參數少了,代碼維護起來方便了一些。
29、去除中間人對象
概念:本文中的”去除中間人對象”是指把在中間關聯而不起不論什么其它作用的類移除,讓有關系的兩個類直接進行交互。
正文:有些時候在我們的代碼會存在一些”幽靈類“。設計模式大師Fowler稱它們為“中間人”類,“中間人”類除了調用別的對象之外不做不論什么事情。所以“中間人”類沒有存在的必要。我們能夠將它們從代碼中刪除,從而讓交互的兩個類直接關聯。
例如以下代碼所看到的,Consumer 類要得到AccountDataProvider 的數據,但中間介入了沒起不論什么作用的AccountManager 類來關聯,所以我們應當移除。
| using LosTechies.DaysOfRefactoring.PullUpField.After;
namespace LosTechies.DaysOfRefactoring.SampleCode.RemoveMiddleMan.Before { public class Consumer { public AccountManager AccountManager { get; set; }
public Consumer(AccountManager accountManager) { AccountManager = accountManager; }
public void Get(int id) { Account account = AccountManager.GetAccount(id); } }
public class AccountManager { public AccountDataProvider DataProvider { get; set; }
public AccountManager(AccountDataProvider dataProvider) { DataProvider = dataProvider; }
public Account GetAccount(int id) { return DataProvider.GetAccount(id); } }
public class AccountDataProvider { public Account GetAccount(int id) { // get account } } }
|
重構后的代碼例如以下所看到的,Consumer 和AccountDataProvider 直接進行關聯,這樣代碼就簡單了。
| using LosTechies.DaysOfRefactoring.PullUpField.After;
namespace LosTechies.DaysOfRefactoring.SampleCode.RemoveMiddleMan.After { public class Consumer { public AccountDataProvider AccountDataProvider { get; set; }
public Consumer(AccountDataProvider dataProvider) { AccountDataProvider = dataProvider; }
public void Get(int id) { Account account = AccountDataProvider.GetAccount(id); } }
public class AccountDataProvider { public Account GetAccount(int id) { // get account } } }
|
總結: ”去除中間人對象“非常多時候都會非常有作用。尤其是在誤用設計模式的代碼中最easy見到,設計模式中的適配器模式和代理模式等都用中間的類是兩者進行關聯,這是比較合理的。由於中間類做了非常多事情,而對於沒有不論什么作用的中間類應該移除。
30、盡快返回
概念: 本文中的”盡快返回”是指把原來復雜的條件推斷等語句用盡快返回的方式簡化代碼。
正文:如首先聲明的是前面講的”分解復雜推斷“。簡單的來說,當你的代碼中有非常深的嵌套條件時,花括號就會在代碼中形成一個長長的箭頭。我們常常在不同的代碼中看到這樣的情況,而且這樣的情況也會擾亂代碼的可讀性。
下代碼所看到的,HasAccess方法里面包括一些嵌套條件,假設再加一些條件或者添加復雜度。那么代碼就非常可能出現幾個問題:1,可讀性差 2。非常easy出現異常 3,性能較差
| using System.Collections.Generic;
|
那么重構上面的代碼也非常easy,假設有可能的話,盡量將條件推斷從方法中移除,我們讓代碼在做處理任務之前先檢查條件。假設條件不滿足就盡快返回,不繼續運行。
以下是重構后的代碼:
| using System.Collections.Generic;
|
總結: 總結:這個重構非常重要,它和前面講的”分解復雜推斷“有些類似,我們在做復雜的處理過程時。要常常考慮這個重構,用好了它,會對我們的幫助非常大。
31、使用多態取代條件推斷
概念:本文中的”使用多態取代條件推斷”是指假設你須要檢查對象的類型或者依據類型運行一些操作時,一種非常好的辦法就是將算法封裝到類中,並利用多態性進行抽象調用。
正文:本文展示了面向對象編程的基礎之中的一個“多態性”, 有時你須要檢查對象的類型或者依據類型運行一些操作時。一種非常好的辦法就是將算法封裝到類中,並利用多態性進行抽象調用。
例如以下代碼所看到的。OrderProcessor 類的ProcessOrder方法依據Customer 的類型分別運行一些操作。正如上面所講的那樣。我們最好將OrderProcessor 類中這些算法(數據或操作)封裝在特定的Customer 子類中。
| using System;
|
重構后的代碼例如以下,每一個Customer 子類都封裝自己的算法。然后OrderProcessor 類的ProcessOrder方法的邏輯也變得簡單而且清晰了。
| using System;
|
總結: ”使用多態取代條件推斷“這個重構在非常多時候會出現設計模式中(常見的工廠家族、策略模式等都能夠看到它的影子)。由於運用它能夠省去非常多的條件推斷,同一時候也能簡化代碼、規范類和對象之間的職責。
