2009-05-23 07:06:08
摘要:Substitute Algorithm:替换你的算法
如果你发现做一件事可以有更清晰的方式,就应该以较清晰的方式取代复杂方式。随着对问题有了更多理解,你往往会发现,在你的原先作法之外,有更简单的解决方案,此时你就需要将方法本体替换为另一个算法。
public string FoundPerson(string[] people)
{
for (int i = 0; i people.Length; i++)
{
if (people[i].Equals(Don))
return Don;
if (people[i].Equals(John))
return John;
if (people[i].Equals(Kent))
return Kent;
}
return ;
}
如果将上面代码换一个算法代码会更清晰。
public string FoundPerson(string[] people)
{
Liststring candidates = new Liststring(people);
for (int i = 0; i people.Length; i++)
{
if (candidates.Contains(people[i]))
return people[i];
}
return ;
}
阅读全文
2009-05-21 10:03:42
摘要:Encapsulate Conditional:封装条件
当代码中充斥着若干条件判断时,代码的真正意图会迷失于这些条件判断之中。这时我喜欢将条件判断提取到一个易于读取的属性或方法中。
public class RemoteControl
{
private string[] Functions { get; set; }
private string Name { get; set; }
private int CreatedYear { get; set; }
public string PerformCoolFunction(string buttonPressed)
{
// Determine if we are controlling some extra function
// that requires special conditions
if (Functions.Length 1 Name == RCA
CreatedYear DateTime.Now.Year - 2)
return doSomething;
}
}
重构之后,代码的可读性更强,意图更明显:
public class RemoteControl
{
private string[] Functions { get; set; }
private string Name { get; set; }
private int CreatedYear { get; set; }
private bool HasExtraFunctions
{
get
{
return Functions.Length 1 Name == RCA
CreatedYear DateTime.Now.Year - 2;
}
}
public string PerformCoolFunction(string buttonPressed)
{
// Determine if we are controlling some extra function
// that requires special conditions
if (HasExtraFunctions)
return doSometh……
阅读全文
2009-05-17 21:02:00
摘要:Break Responsibilities:责任拆分
把一个类的多个职责进行拆分,这贯彻了单一职责原则(SRP)。尽管对于如何划分“职责”经常存在争论,但应用这项重构还是十分简单的。我这里并不会回答划分职责的问题,只是演示一个结构清晰的示例,将类划分为多个负责具体职责的类。
public class Video
{
public void PayFee(decimal fee)
{
}
public void RentVideo(Video video, Customer customer)
{
customer.Videos.Add(video);
}
public decimal CalculateBalance(Customer customer)
{
return customer.LateFees.Sum();
}
}
public class Customer
{
public IListdecimal LateFees { get; set; }
public IListVideo Videos { get; set; }
}
如你所见,Video类包含两个职责,一个负责处理录像租赁,另一个负责管理管理用户的租赁总数。要分离职责,我们可以将用户的逻辑转移到用户类中。
public class Video
{
public void RentVideo(Video video, Customer customer)
{
customer.Videos.Add(video);
}
}
public class Customer
{
public IListdecimal LateFees { get; set; }
public IListVideo Videos { get; set; }
public void PayFee(decimal fee)
{
}
public decimal CalculateBalance(Customer customer)
{
return customer.LateFees.Sum();
}
}
阅读全文
2009-05-14 23:03:01
摘要:Extract Method Object:提炼方法对象
当你尝试进行提炼方法的重构时,需要引入大量的方法。在一个方法中使用众多的本地变量有时会使代码变得丑陋。因此最好使用提炼方法对象这个重构,将执行任务的逻辑分开。
public class OrderLineItem
{
public decimal Price { get; private set; }
}
public class Order
{
private IListOrderLineItem OrderLineItems { get; set; }
private IListdecimal Discounts { get; set; }
private decimal Tax { get; set; }
public decimal Calculate()
{
decimal subTotal = 0m;
// Total up line items
foreach (OrderLineItem lineItem in OrderLineItems)
{
subTotal += lineItem.Price;
}
// Subtract Discounts
foreach (decimal discount in Discounts)
subTotal -= discount;
// Calculate Tax
decimal tax = subTotal * Tax;
// Calculate GrandTotal
decimal grandTotal = subTotal + tax;
return grandTotal;
}
}
我们通过构造函数,将返回计算结果的类的引用传递给包含多个计算方法的……
阅读全文
2009-05-11 16:34:10
摘要:Introduce Explaining Variable:引入解释性变量
你有一个表达式,有可能非常复杂而难以阅读。这种情况下,临时变量可以帮助你将表达式分解为比较容易管理的形式。将赋值表达式(或其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式的用途。
if ((platform.ToUpper().IndexOf(MAC) -1)
(browser.ToUpper().IndexOf(IE)) -1
WasInitialized() resize 0)
{
//do something
}
在上面的代码中 if 语句内的条件判断你必须完全读完,也不一定很快就能知道它们的用意,但是按下面方式重构后,你可能很快就能读懂代码:
bool isMacOs = platform.ToUpper().IndexOf(MAC) -1;
bool isIEBrowser = browser.ToUpper().IndexOf(IE) -1;
bool wasResized = resize 0;
if (isMacOs isIEBrowser WasInitialized() wasResized)
{
//do something
}
阅读全文
2009-05-09 00:02:24
摘要:Break Dependencies:打破依赖
有些单元测试需要恰当的测试“缝隙”(test seam)来模拟/隔离一些不想被测试的部分。如果你正想在代码中引入这种单元测试,那么今天介绍的重构就十分有用。在这个例子中,我们的客户端代码使用一个静态类来实现功能。但当需要单元测试时,问题就来了。我们无法在单元测试中模拟静态类。解决的方法是使用一个接口将静态类包装起来,形成一个缝隙来切断与静态类之间的依赖。
public class AnimalFeedingService
{
private bool FoodBowlEmpty { get; set; }
public void Feed()
{
if (FoodBowlEmpty)
Feeder.ReplenishFood();
// more code to feed the animal
}
}
public static class Feeder
{
public static void ReplenishFood()
{
// fill up bowl
}
}
重构时我们所要做的就是引入一个接口和简单调用上面那个静态类的类。因此行为还是一样的,只是调用的方式产生了变化。这是一个不错的重构起始点,也是向代码添加单元测试的简单方式。
public class AnimalFeedingService
{
public IFeederService FeederService { get; set; }
public AnimalFeedingService(IFeederService feederService)
{
FeederService = feederService;
}
private bool FoodBowlEmpty { get; set; }
public void Feed()
{
if (FoodBowlEmpty)
FeederService.ReplenishFood();
// more code to feed the animal
}
}
public interface IFeederService
{
void ReplenishFood();
}
public class FeederService……
阅读全文
2009-05-07 15:43:21
摘要:Switch to Strategy:策略模式转换switch分支
switch 语句块很大,并且会随时引入新的判断条件。这时,最好使用策略模式将每个条件封装到单独的类中。
实现策略模式的方式是很多的。我在这里介绍的策略重构使用的是字典策略,这么做的好处是调用者不必修改原来的代码。
public class ClientCode
{
public decimal CalculateShipping()
{
ShippingInfo shippingInfo = new ShippingInfo();
return shippingInfo.CalculateShippingAmount(State.Alaska);
}
}
public enum State
{
Alaska,
NewYork,
Florida
}
public class ShippingInfo
{
public decimal CalculateShippingAmount(State shipToState)
{
switch (shipToState)
{
case State.Alaska:
return GetAlaskaShippingAmount();
case State.NewYork:
return GetNewYorkShippingAmount();
case State.Florida:
return GetFloridaShippingAmount();
default:
return 0m;
}
}
private decimal GetAlaskaShippingAmount()
{
return 15m;
}
private decimal GetNewYorkShippingAmount()
{
return 10m;
}
private decimal GetFloridaShippingAmount()
{
return 3m;
}
}
要应用该重构,需将每个测试条件至于单独的类中,这些类实现了一个共同的接口。然后将枚举作为字典的键,这样就可以获取正确的实现,并执行其代码了。以后如果希望添加新的条件,只需添加新的实现类,并将其添……
阅读全文
2009-05-06 13:22:28
摘要:Inline Method:将方法内联化
一个方法,其本体如果比其名称更清楚易懂。在方法调用点插入方法本体,然后移除该方法
int GetRating()
{
return (MoreThanFiveLateDeliveries()) ? 2 : 1;
}
bool MoreThanFiveLateDeliveries()
{
return _numberOfLateDeliveries 5;
}
你会发现通过下面重构合并上面的两个方法代码会更具可读性:
int GetRating()
{
return (_numberOfLateDeliveries 5) ? 2 : 1;
}
有时候你会遇到某个方法,其内部代码和方法名称同样清晰易懂。你就可以去掉这个方法,直接使用其中的代码。间接性可能带来帮助,但非必须的间接性总是让人不舒服。
阅读全文
2009-05-05 15:50:24
摘要:Extract Method:提炼方法
你有一段代码可以被组织在一起并独立出来,将这段代码放进一个独立方法中,并让方法名称解释该方法的用途。
public class Receipt
{
private IListdecimal Discounts { get; set; }
private IListdecimal ItemTotals { get; set; }
public decimal CalculateGrandTotal()
{
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函数一共做了3件不同的事情:计算总额、折扣和发票税额。开发者为了搞清楚每个功能如何处理而不得不将代码从头看到尾。相比于此,向下面的代码那样将每个任务分解成单独的函数则要节省更多时间,也更具可读性:
public class Receipt
{
private IListdecimal Discounts { get; set; }
private IListdecimal ItemTotals { get; set; }
public decimal CalculateGrandTotal()
{
decimal subTotal = CalculateSubTotal();
subTotal = CalculateDiscounts(subTotal);
subTotal = CalculateTax(subTotal);……
阅读全文