C#重构经典全面汇总
1、 封装集合
概念:本文所讲的封装集合就是把集合进行封装,仅仅提供调用端须要的接口。
正文:在非常多时候,我们都不希望把一些不必要的操作暴露给调用端,仅仅须要给它所须要的操作或数据即可,那么做法就是封装。这个重构在微软的代码库也常常遇到。
比方最经典的属性对字段的封装就是一个非常好的样例,那么以下我们将看到对集合的封装。例如以下代码所看到的,调用端仅仅须要一个集合的信息,而我们则提供了一个IList的集合。大家都知道IList具有对集合的全部操作,所以这会带来非常多隐患。最好的做法就是对它进行重构。
using System.Collections.Generic; namespace LosTechies.DaysOfRefactoring.EncapsulateCollection.Before { public class Order { private List<OrderLine> _orderLines; private double _orderTotal; public IList<OrderLine> OrderLines { get { return } public void AddOrderLine(OrderLine { _orderTotal += orderLine.Total; _orderLines.Add(orderLine); } public void RemoveOrderLine(OrderLine { orderLine = _orderLines.Find(o => o == orderLine); if (orderLine == return; _orderTotal -= orderLine.Total; _orderLines.Remove(orderLine); } } public class OrderLine { public double Total { } } |
那么重构之后,我们把IList换成了IEnumerable,大家都知道仅仅包含一个返回值为IEnumerator的GetEnumerator()方法,所以这样仅仅能遍历取出它的值。而不能对这个集合做出改变,这正是我们所须要的结果,详细代码例如以下:
using System.Collections.Generic; namespace LosTechies.DaysOfRefactoring.EncapsulateCollection.After { public class Order { private List<OrderLine> _orderLines; private double _orderTotal; public IEnumerable<OrderLine> OrderLines { get { return } public void AddOrderLine(OrderLine { _orderTotal += orderLine.Total; _orderLines.Add(orderLine); } public void RemoveOrderLine(OrderLine { orderLine = _orderLines.Find(o => o == orderLine); if (orderLine == return; _orderTotal -= orderLine.Total; _orderLines.Remove(orderLine); } } public class OrderLine { public double Total { } } |
总结:这个样例非常easy让我们想到曾经系统间耦合常喜欢用数据库。每一个系统都会操作数据库。而且有些系统还会对数据库的表结构或字段进行改动。那么这非常easy就会造成维护的地狱,非常明智的一个做法就是使用SOA来隔开这些耦合,让一些仅仅须要数据展示的系统得到自己须要的数据就可以。
2、 移动方法
概念:本文所讲的移动方法就是方法放在合适的位置(通常指放在合适的类中)。
正文:移动方法是一个非常easy也非经常见的重构,仅仅要是系统就会存在非常多类。那么类里面包含非常多方法。假设一个方法经常被另外一个类使用(比本身的类使用还多)或者这种方法本身就不应该放在这个类里面,那么这个适合应该考虑把它移到合适的类中。代码例如以下:
namespace LosTechies.DaysOfRefactoring.MoveMethod.Before { public class BankAccount { public BankAccount(int { AccountAge = accountAge; CreditScore = creditScore; AccountInterest = accountInterest; } public int AccountAge { public int CreditScore { public AccountInterest public double CalculateInterestRate() { if (CreditScore > 800) return 0.02; if (AccountAge > 10) return 0.03; return 0.05; } } public class AccountInterest { public BankAccount public AccountInterest(BankAccount { Account = account; } public double InterestRate { get { return } public bool IntroductoryRate { get { return } } } |
移动以后大家能够看到BankAccount类的职责也单一,同一时候CalculateInterestRate也放到了常常使用且适合它的类中了,所以此重构是一个比較好的重构。能让整个代码变得更加合理。
namespace LosTechies.DaysOfRefactoring.MoveMethod.After { public class AccountInterest { public BankAccount Account { public AccountInterest(BankAccount account) { Account = account; } public double InterestRate { get { return } public bool IntroductoryRate { get { return } public double CalculateInterestRate() { if (Account.CreditScore > 800) return 0.02; if (Account.AccountAge > 10) return 0.03; return 0.05; } } } namespace LosTechies.DaysOfRefactoring.MoveMethod.After { public class BankAccount { public BankAccount(int { AccountAge = accountAge; CreditScore = creditScore; AccountInterest = accountInterest; } public int AccountAge { public int CreditScore { public AccountInterest AccountInterest { } } |
总结:这个重构法则在非常多时候能让我们把代码组织的结构调整得更合理。同一时候也能给以后的维护带来方便。
3、 提升方法
概念:提升方法是指将一个非常多继承类都要用到的方法提升到基类中。
正文:提升方法是指将一个非常多继承类都要用到的方法提升到基类中,这样就能降低代码量,同一时候让类的结构更清晰。例如以下代码所看到的,Turn方法在子类Car和Motorcycle
都会用到,由于Vehicle 都会有这种方法,所以我们就会想到把它提到基类中。
namespace LosTechies.DaysOfRefactoring.PullUpMethod.Before { public abstract class Vehicle { // other methods } public class Car { public void Turn(Direction { // code here } } public class Motorcycle { } public enum Direction { Left, Right } } |
重构后的代码例如以下。那么如今Car 和Motorcycle
都具有Turn这种方法。假设这种方法改动也仅仅须要改动基类就可以,所以给维护和以后的重构带来了方便。
namespace LosTechies.DaysOfRefactoring.PullUpMethod.After { public abstract class Vehicle { public void Turn(Direction { // code here } } public class Car { } public class Motorcycle { } public enum Direction { Left, Right } } |
总结:这个重构要依据详细情况使用。假设不是每一个子类都有这种方法的话,能够考虑使用接口或者其它方式。
4、 减少方法
概念:本文中的减少方法和前篇的提升方法整好相反,也就是把个别子类使用到的方法从基类移到子类里面去。
正文:例如以下代码所看到的,Animal 类中的方法Bark仅仅有在其子类Dog 中使用,所以最好的方案就是把这种方法移到子类Dog 中。
namespace LosTechies.DaysOfRefactoring.PushDownMethod.Before { public abstract class Animal { public void Bark() { // code to bark } } public class Dog { } public class Cat { } } |
重构后的代码例如以下,同一时候假设在父类Animal 中假设没有其它的字段或者公用方法的话,能够考虑把Bark方法做成一个接口。从而去掉Animal
类。
namespace LosTechies.DaysOfRefactoring.PushDownMethod.After { public abstract class Animal { } public class Dog { public void Bark() { // code to bark } } public class Cat { } } |
总结:面向对象三大特征(继承、封装、多态)非常多时候能够帮助我们,但同一时候也可能会造成使用过度或者使用不当,所以怎样把握好设计。这个就变得至关重要。在什么时候使用继承的方式,在什么时候使用组合和聚合。接口和继承类的选择等久成了我们的重点。
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; using LosTechies.DaysOfRefactoring.EncapsulateCollection.After; using LosTechies.DaysOfRefactoring.SampleCode.BreakMethod.After; using Customer = LosTechies.DaysOfRefactoring.BreakResponsibilities.After.Customer; namespace LosTechies.DaysOfRefactoring.SampleCode.RemoveGodClasses.Before { public class CustomerService { public decimal CalculateOrderDiscount(IEnumerable<Product> products, Customer customer) { // do work } public bool CustomerIsValid(Customer customer, Order order) { // do work } public IEnumerable<string> GatherOrderErrors(IEnumerable<Product> products, Customer customer) { // do work } public void Register(Customer customer) { // do work } public void ForgotPassword(Customer customer) { // do work } } } |
我们看到要重构上面的代码是非常easy的,仅仅要将相关的方法按职责分派到相应的类中就可以,带来的优点就是这会降低代码的颗粒度并降低未来维护代码的成本。以下是重构后的代码。它将上面
的代码依照职责分为了两个不同的类。
using System.Collections.Generic; using LosTechies.DaysOfRefactoring.EncapsulateCollection.After; using LosTechies.DaysOfRefactoring.SampleCode.BreakMethod.After; using Customer = LosTechies.DaysOfRefactoring.BreakResponsibilities.After.Customer; namespace LosTechies.DaysOfRefactoring.SampleCode.RemoveGodClasses.After { public class CustomerOrderService { public decimal CalculateOrderDiscount(IEnumerable<Product> products, Customer customer) { // do work } public bool CustomerIsValid(Customer customer, Order order) { // do work } public IEnumerable<string> GatherOrderErrors(IEnumerable<Product> products, Customer customer) { // do work } } public class CustomerRegistrationService { public void Register(Customer customer) { // do work } public void ForgotPassword(Customer customer) { // do work } } } |
总结: ”去除上帝类“是我们常常easy造成的,第一是由于简便,看到有一个现成的类,大家都会喜欢把代码往里面写,最后导致越写越大。而且声明功能都有,这样即减少了可读性,也造成了维护的负担。
28、为布尔方法命名
概念:本文中的”为布尔方法命名”是指假设一个方法带有大量的bool
參数时,能够依据bool 參数的数量,提取出若干个独立的方法来简化參数。
正文:我们如今要说的重构并非普通字面意义上的重构。它有非常多值得讨论的地方。
当一个方法带有大量的bool
參数时,会导致方法非常easy被误解并产生非预期的行为,
依据布尔型參数的数量,我们能够决定提取出若干个独立的方法来。
详细代码例如以下:
using LosTechies.DaysOfRefactoring.BreakResponsibilities.After; namespace LosTechies.DaysOfRefactoring.SampleCode.RenameBooleanMethod.Before { public class BankAccount { public void CreateAccount(Customer customer, { // do work } } } |
我们能够将上面的bool參数以独立方法的形式暴露给调用端以提高代码的可读性,同一时候我们还须要将原来的方法改为private以限制其可訪问性。显然我们关于要
提取的独立方法会有一个非常大的排列组合,这是一大缺点,所以我们能够考虑引入”參数对象“重构。
using LosTechies.DaysOfRefactoring.BreakResponsibilities.After; namespace LosTechies.DaysOfRefactoring.SampleCode.RenameBooleanMethod.After { public class BankAccount { public void CreateAccountWithChecking(Customer customer) { CreateAccount(customer, true, } public void CreateAccountWithCheckingAndSavings(Customer customer) { CreateAccount(customer, true, } private void CreateAccount(Customer customer, { // do work } } } |
总结: ”为布尔方法命名“这个重构在非常多时候都不经常使用,假设用户的參数可枚举,我们通常会枚举它的值。只是使用这样的重构也有优点,就是分解开来以后,方法多了。參数少了,代码维护起来方便了一些。
29、去除中间人对象
概念:本文中的”去除中间人对象”是指把在中间关联而不起不论什么其它作用的类移除,让有关系的两个类直接进行交互。
正文:有些时候在我们的代码会存在一些”幽灵类“。设计模式大师Fowler称它们为“中间人”类,“中间人”类除了调用别的对象之外不做不论什么事情。所以“中间人”类没有存在的必要。我们能够将它们从代码中删除,从而让交互的两个类直接关联。
例如以下代码所看到的,Consumer 类要得到AccountDataProvider
的数据,但中间介入了没起不论什么作用的AccountManager 类来关联,所以我们应当移除。
using LosTechies.DaysOfRefactoring.PullUpField.After; namespace LosTechies.DaysOfRefactoring.SampleCode.RemoveMiddleMan.Before { public class Consumer { public AccountManager public Consumer(AccountManager { AccountManager = accountManager; } public void Get(int { Account account = AccountManager.GetAccount(id); } } public class AccountManager { public AccountDataProvider public AccountManager(AccountDataProvider { DataProvider = dataProvider; } public Account GetAccount(int { return DataProvider.GetAccount(id); } } public class AccountDataProvider { public Account GetAccount(int { // get account } } } |
重构后的代码例如以下所看到的,Consumer 和AccountDataProvider 直接进行关联,这样代码就简单了。
using LosTechies.DaysOfRefactoring.PullUpField.After; namespace LosTechies.DaysOfRefactoring.SampleCode.RemoveMiddleMan.After { public class Consumer { public AccountDataProvider public Consumer(AccountDataProvider { AccountDataProvider = dataProvider; } public void Get(int { Account account = AccountDataProvider.GetAccount(id); } } public class AccountDataProvider { public Account GetAccount(int { // get account } } } |
总结: ”去除中间人对象“非常多时候都会非常有作用。尤其是在误用设计模式的代码中最easy见到,设计模式中的适配器模式和代理模式等都用中间的类是两者进行关联,这是比較合理的。由于中间类做了非常多事情,而对于没有不论什么作用的中间类应该移除。
30、尽快返回
概念: 本文中的”尽快返回”是指把原来复杂的条件推断等语句用尽快返回的方式简化代码。
正文:如首先声明的是前面讲的”分解复杂推断“。简单的来说,当你的代码中有非常深的嵌套条件时,花括号就会在代码中形成一个长长的箭头。我们常常在不同的代码中看到这样的情况,而且这样的情况也会扰乱代码的可读性。
下代码所看到的,HasAccess方法里面包括一些嵌套条件,假设再加一些条件或者添加复杂度。那么代码就非常可能出现几个问题:1,可读性差 2。非常easy出现异常 3,性能较差
using System.Collections.Generic; using System.Linq; using LosTechies.DaysOfRefactoring.SampleCode.BreakMethod.After; using Customer = LosTechies.DaysOfRefactoring.BreakResponsibilities.After.Customer; namespace LosTechies.DaysOfRefactoring.SampleCode.ReturnASAP.Before { public class Order { public Customer Customer { public decimal CalculateOrder(Customer customer, { Customer = customer; decimal orderTotal = 0m; if (products.Count() > 0) { orderTotal = products.Sum(p => p.Price); if (discounts > 0) { orderTotal -= discounts; } } return orderTotal; } } } |
那么重构上面的代码也非常easy,假设有可能的话,尽量将条件推断从方法中移除,我们让代码在做处理任务之前先检查条件。假设条件不满足就尽快返回,不继续运行。
以下是重构后的代码:
using System.Collections.Generic; using System.Linq; using LosTechies.DaysOfRefactoring.SampleCode.BreakMethod.After; using Customer = LosTechies.DaysOfRefactoring.BreakResponsibilities.After.Customer; namespace LosTechies.DaysOfRefactoring.SampleCode.ReturnASAP.After { public class Order { public Customer Customer { public decimal CalculateOrder(Customer customer, { if (products.Count() == 0) return 0; Customer = customer; decimal orderTotal = products.Sum(p => p.Price); if (discounts == 0) return orderTotal; orderTotal -= discounts; return orderTotal; } } } |
总结: 总结:这个重构非常重要,它和前面讲的”分解复杂推断“有些类似,我们在做复杂的处理过程时。要常常考虑这个重构,用好了它,会对我们的帮助非常大。
31、使用多态取代条件推断
概念:本文中的”使用多态取代条件推断”是指假设你须要检查对象的类型或者依据类型运行一些操作时,一种非常好的办法就是将算法封装到类中,并利用多态性进行抽象调用。
正文:本文展示了面向对象编程的基础之中的一个“多态性”, 有时你须要检查对象的类型或者依据类型运行一些操作时。一种非常好的办法就是将算法封装到类中,并利用多态性进行抽象调用。
例如以下代码所看到的。OrderProcessor 类的ProcessOrder方法依据Customer 的类型分别运行一些操作。正如上面所讲的那样。我们最好将OrderProcessor 类中这些算法(数据或操作)封装在特定的Customer 子类中。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using LosTechies.DaysOfRefactoring.SampleCode.BreakMethod.After; namespace LosTechies.DaysOfRefactoring.SampleCode.ReplaceWithPolymorphism.Before { public abstract class Customer { } public class Employee { } public class NonEmployee { } public class OrderProcessor { public decimal ProcessOrder(Customer { // do some processing of order decimal orderTotal = products.Sum(p => p.Price); Type customerType = customer.GetType(); if (customerType == { orderTotal -= orderTotal * 0.15m; } else if (customerType == { orderTotal -= orderTotal * 0.05m; } return orderTotal; } } } |
重构后的代码例如以下,每一个Customer 子类都封装自己的算法。然后OrderProcessor 类的ProcessOrder方法的逻辑也变得简单而且清晰了。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using LosTechies.DaysOfRefactoring.SampleCode.BreakMethod.After; namespace LosTechies.DaysOfRefactoring.SampleCode.ReplaceWithPolymorphism.After { public abstract class Customer { public abstract decimal DiscountPercentage { } public class Employee { public override decimal DiscountPercentage { get { return } } public class NonEmployee { public override decimal DiscountPercentage { get { return } } public class OrderProcessor { public decimal ProcessOrder(Customer { // do some processing of order decimal orderTotal = products.Sum(p => p.Price); orderTotal -= orderTotal * customer.DiscountPercentage; return orderTotal; } } } |
总结: ”使用多态取代条件推断“这个重构在非常多时候会出现设计模式中(常见的工厂家族、策略模式等都能够看到它的影子)。由于运用它能够省去非常多的条件推断,同一时候也能简化代码、规范类和对象之间的职责。