2009-10-17 17:06:14
摘要:Replace conditional with Polymorphism:以多态取代条件式
多态(Polymorphism)是面向对象编程的基本概念之一。在这里,是指在进行类型检查和执行某些类型操作时,最好将算法封装在类中,并且使用多态来对代码中的调用进行抽象。
public abstract class Customer
{
}
public class Employee : Customer
{
}
public class NonEmployee : Customer
{
}
public class OrderProcessor
{
public decimal ProcessOrder(Customer customer, IEnumerableProduct products)
{
// do some processing of order
decimal orderTotal = products.Sum(p = p.Price);
Type customerType = customer.GetType();
if (customerType == typeof(Employee))
{
orderTotal -= orderTotal * 0.15m;
}
else if (customerType == typeof(NonEmployee))
{
orderTotal -= orderTotal * 0.05m;
}
return orderTotal;
}
}
如你所见,我们没有利用已有的继承层次进行计算,而是使用了违反SRP 原则的执行方式。要进行重构,我们只需将百分率的计算置于实际的customer类型之中。我知道这只是一项补救措施,但我还是会这么做,就像在代码中那样。
public abstract class Customer
{
public abstract decimal DiscountPercentage { get; }
}
public class Employee : Customer
{
public override decimal DiscountPercentage
{
get { return 0.15m; }
}
}
publi……
阅读全文
2009-10-11 16:18:28
摘要:Replace Inheritance with Delegation:以委托取代继承
某个子类只使用父类接口中的一部分,或者根本不需要继承而来的数据。在子类中新建一个字段用以保存父类;调整子类方法,令它改成委托父类;然后去掉两者之间的继承关系。
public class Sanitation
{
public string WashHands()
{
return Cleaned!;
}
}
public class Child : Sanitation
{
}
在该例中,Child 并不是Sanitation,因此这样的继承层次是毫无意义的。我们可以这样重构:在Child 的构造函数里实现一个Sanitation实例,并将方法的调用委托给这个实例。如果你使用依赖注入,可以通过构造函数传递Sanitation实例,尽管在我看来还要向IoC容器注册模型是一种坏味道,但领会精神就可以了。继承只能用于严格的继承场景,并不是用来快速编写代码的工具。
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();
}
}
阅读全文
2009-10-03 14:30:49
摘要:Collapse Hierarchy:折叠继承体系
所谓重构继承体系,往往是将方法和字段在体系中上下移动。完成这些动作后,你很可能发现某个子类并未带来该有的价值,因此需要把两个类合并。
public class Website
{
public string Title { get; set; }
public string Description { get; set; }
public IEnumerableWebpage Pages { get; set; }
}
public class StudentWebsite : Website
{
public bool IsActive { get; set; }
}
这里的子类并没有过多的功能,只是表示站点是否激活。这时我们会意识到判断站点是否激活的功能应该是通用的。因此可以将子类的功能放回到Website 中,并删除StudentWebsite 类型。
public class Website
{
public string Title { get; set; }
public string Description { get; set; }
public IEnumerableWebpage Pages { get; set; }
public bool IsActive { get; set; }
}
阅读全文
2009-09-30 18:54:22
摘要:Encapsulate Collection:封装群集
有个方法返回一个群集。让这个方法返回该群集的一个只读映件,并在这个类中提供添加/删除(add/remove)群集元素的方法。因此,以可迭代但不直接在集合上进行操作的方式来暴露集合,是个不错的主意。我们来看代码:
public class Order
{
private int _orderTotal;
private ListOrderLine _orderLines;
public IEnumerableOrderLine OrderLines
{
get { return _orderLines; }
}
public void AddOrderLine(OrderLine orderLine)
{
_orderTotal += orderLine.Total;
_orderLines.Add(orderLine);
}
public void RemoveOrderLine(OrderLine orderLine)
{
orderLine = _orderLines.Find(o = o == orderLine);
if (orderLine == null) return;
_orderTotal -= orderLine.Total;
_orderLines.Remove(orderLine);
}
}
如你所见,我们对集合进行了封装,没有将Add/Remove 方法暴露给类的使用者。在.NET Framework中,有些类如ReadOnlyCollection,会由于封装集合而产生不同的行为,但它们各自都有防止误解的说明。这是一个非常简单但却极具价值的重构,可以确保用户不会误用你暴露的集合,避免代码中的一些bug。
阅读全文
2009-09-25 11:58:55
摘要:Remove Middle Man:删除中间人
有时你的代码里可能会存在一些“Phantom”或“Ghost”类,Fowler 称之为“中间人(Middle Man)”。这些中间人类仅仅简单地将调用委托给其他组件,除此之外没有任何功能。就应该删除这个类,让客户直接调用受托类。
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
}
}
最终结果已经足够简单了。我们只需要移除中间人对象,将原始调用指向实际的接收者。
public class Consumer
{
public AccountDataProvider AccountDataProvider { get; set; }
public Consumer(AccountDataProvider dataProvider)
{
AccountDataProvider = dataProvider;
}
public void Get(int id)
{
Acc……
阅读全文
2009-09-22 23:17:08
摘要:Extract Interface:提炼接口
如果你发现多于一个类使用另外一个类的某些方法,引入接口解除这种依赖往往十分有用。该重构实现起来非常简单,并且能够享受到松耦合带来的好处。
public class ClassRegistration
{
public void Create()
{
// create registration code
}
public void Transfer()
{
// class transfer code
}
public decimal Total { get; private set; }
}
public class RegistrationProcessor
{
public decimal ProcessRegistration(ClassRegistration registration)
{
registration.Create();
return registration.Total;
}
}
在下面的代码中,你可以看到我提取出了消费者所使用的两个方法,并将其置于一个接口中。现在消费者不必关心和了解实现了这些方法的类。我们解除了消费者与实际实现之间的耦合,使其只依赖于我们创建的契约。
public interface IClassRegistration
{
void Create();
decimal Total { get; }
}
public class ClassRegistration : IClassRegistration
{
public void Create()
{
// create registration code
}
public void Transfer()
{
// class transfer code
}
public decimal Total { get; private set; }
}
public class RegistrationProcessor
{
public decimal ProcessRegistration(IClassRegistration registration)
{
registration.Create();
return registration.……
阅读全文
2009-09-18 20:00:47
摘要:Extract Subclass:提炼子类
当一个类中的某些方法并不是面向所有的类时,新建一个子类,将只属于某些实体的方法提炼到新建的子类中。
public class Registration
{
public NonRegistrationAction Action { get; set; }
public decimal RegistrationTotal { get; set; }
public string Notes { get; set; }
public string Description { get; set; }
public DateTime RegistrationDate { get; set; }
}
当使用了该类之后,我们就会意识到问题所在——它应用于两个完全不同的场景。属性NonRegistrationAction和Notes 只有在处理与普通注册略有不同的NonRegistration 时才会使用。因此,我们可以提取一个子类,并将这两个属性转移到NonRegistration类中,这样才更合适。
public class Registration
{
public decimal RegistrationTotal { get; set; }
public string Description { get; set; }
public DateTime RegistrationDate { get; set; }
}
public class NonRegistration : Registration
{
public NonRegistrationAction Action { get; set; }
public string Notes { get; set; }
}
阅读全文
2009-09-11 15:55:52
摘要:Extract Superclass:提炼父类
两个类做类似的事情。可以为这个两个类建立一个父类,将相同特性移至到父类。
public class Dog
{
public void EatFood()
{
// eat some food
}
public void Groom()
{
// perform grooming
}
}
重构之后,我们仅仅将需要的方法转移到了一个新的基类中。这很类似“Pull Up”重构,只是在重构之前,并不存在基类。
public class Animal
{
public void EatFood()
{
// eat some food
}
public void Groom()
{
// perform grooming
}
}
public class Dog : Animal
{
}
在建立这些具有共通性的类之前,你往往无法发现这样的共通性,因此你经常会在具体共通性的类存在后,再开始建立其间的继承结构。
阅读全文
2009-09-07 11:21:48
摘要:Replace Magic Number with Symbolic Constant:以常量取代特殊数值
在计算科学中,经常会出现一些特殊数值,比如圆周率。如果你需要在不同的地方引用一个特殊数值,不要直接用这个字面数值,请创造一个常量,根据其意义为它命名,并将这个字面数值替换为这个常量。
public double PotentialEnergy(double mass, double height)
{
return mass * 9.81 * height;
}
将上面实例中的9.81替换成一个常量。许多语言都允许声明常量,常量不会造成任何性能开销,却可以大大提高代码的可读性。上面这个实例中,如果9.81这个特殊数值发生变化,你没有引入常量的话,就需要全部修改一遍。而引入常量就只需要在一个地方做修改。
public double PotentialEnergy(double mass, double height)
{
return mass * GRAVITATIONAL * height;
}
阅读全文
2009-09-03 16:09:16
摘要:Move Method:搬移方法
你的程序中,有个方法与所驻class之外的另一个class进行更多交流:调用后者,或被后者调用。那么请将方法迁移到合适的class中。
public class BankAccount
{
public BankAccount(int accountAge, int creditScore,
AccountInterest accountInterest)
{
AccountAge = accountAge;
CreditScore = creditScore;
AccountInterest = accountInterest;
}
public int AccountAge { get; private set; }
public int CreditScore { get; private set; }
public AccountInterest AccountInterest { get; private set; }
public double CalculateInterestRate()
{
if (CreditScore 800)
return 0.02;
if (AccountAge 10)
return 0.03;
return 0.05;
}
}
public class AccountInterest
{
public BankAccount Account { get; private set; }
public AccountInterest(BankAccount account)
{
Account = account;
}
public double InterestRate
{
get { return Account.CalculateInterestRate(); }
}
public bool IntroductoryRate
{
get { return Account.CalculateInterestRate() 0.05; }
}
}
这里值得注意的是BankAccount.CalculateInterest 方法。当一个方法被其他类使用比在它所在类中的使用还要频繁时,我们就需要使用……
阅读全文