2009-08-30 14:23:03
摘要:Remove God Classes:移除工具类
在传统的代码库中,我们常常会看到一些违反了SRP原则的类。这些类通常以Utils或Manager结尾,有时也没有这么明显的特征而仅仅是普通的包含多个功能的类。这种God 类还有一个特征,使用语句或注释将代码分隔为多个不同角色的分组,而这些角色正是这一个类所扮演的。久而久之,这些类成为了那些没有时间放置到恰当类中的方法的垃圾桶。这时的重构需要将方法分解成多个负责单一职责的类。
public class CustomerService
{
public decimal CalculateOrderDiscount(IEnumerableProduct products, Customer customer)
{
// do work
}
public bool CustomerIsValid(Customer customer, Order order)
{
// do work
}
public IEnumerablestring GatherOrderErrors(IEnumerableProduct products, Customer customer)
{
// do work
}
public void Register(Customer customer)
{
// do work
}
public void ForgotPassword(Customer customer)
{
// do work
}
}
使用该重构是非常简单明了的,只需把相关方法提取出来并放置到负责相应职责的类中即可。这使得类的粒度更细、职责更分明、日后的维护更方便。上例的代码最终被分解为两个类:
public class CustomerOrderService
{
public decimal CalculateOrderDiscount(IEnumerableProduct products, Customer customer)
{
// do work
}
public bool CustomerIsValid(Customer customer, Order order)
{
// do work
}
public IEnumerablestri……
阅读全文
2009-08-23 19:02:42
摘要:Push Down Method:下移方法
与下移字段一样,如果父类中某个方法只与部分子类有关,将这个方法移到相关的那些子类去。
public abstract class Animal
{
public void Bark()
{
// code to bark
}
}
public class Dog : Animal
{
}
public class Cat : Animal
{
}
这里的基类有一个Bark方法。或许我们的猫咪们一时半会也没法学会汪汪叫(bark),因此Cat 类中不再需要这个功能了。尽管基类不需要这个方法,但在显式处理Dog 类时也许还需要,因此我们将Bark 方法“下移”到Dog 类中。这时,有必要评估Animal基类中是否还有其他行为。如果没有,则是一个将Animal抽象类转换成接口的好时机。因为契约中不需要任何代码,可以认为是一个标记接口。
public abstract class Animal
{
}
public class Dog : Animal
{
public void Bark()
{
// code to bark
}
}
public class Cat : Animal
{
}
阅读全文
2009-08-19 16:32:34
摘要:Push Down Field:下移字段
与上移字段相反的重构是下移字段,如果父类中某个字段只被部分子类用到,将这个字段移到需要它的那些子类去。
public abstract class Task
{
protected string _resolution;
}
public class BugTask : Task
{
}
public class FeatureTask : Task
{
}
在这个例子中,基类中的一个字符串字段只被一个子类使用,因此可以进行下移。只要没有其他子类使用基类的字段时,就应该立即执行该重构。保留的时间越长,就越有可能不去重构而保持原样。
public abstract class Task
{
}
public class BugTask : Task
{
private string _resolution;
}
public class FeatureTask : Task
{
}
阅读全文
2009-08-16 18:16:45
摘要:Pull Up Mothod:方法上移
今天介绍的重构方法和上一篇字段上移十分类似。我们今天处理的不是字段,而是方法。
有些方法,在各个子类中产生完全相同的结果,将该方法移至到父类。
public abstract class Vehicle
{
// other methods
}
public class Car : Vehicle
{
public void Turn(Direction direction)
{
// code here
}
}
public class Motorcycle : Vehicle
{
}
public enum Direction
{
Left,
Right
}
如你所见,目前只有Car类中包含Turn方法,但我们也希望在Motorcycle 类中使用。因此,如果没有基类,我们就创建一个基类并将该方法“上移”到基类中,这样两个类就都可以使用Turn 方法了。这样做唯一的缺点是扩充了基类的接口、增加了其复杂性,因此需谨慎使用。只有当一个以上的子类需要使用该方法时才需要进行迁移。如果滥用继承,系统将会很快崩溃。这时你应该使用组合代替继承。重构之后的代码如下:
public abstract class Vehicle
{
public void Turn(Direction direction)
{
// code here
}
}
public class Car : Vehicle
{
}
public class Motorcycle : Vehicle
{
}
public enum Direction
{
Left,
Right
}
阅读全文
2009-08-15 17:44:12
摘要:Pull Up Field:字段上移
两个子类拥有相同的字段,将此一字段上移到父类。
public abstract class Account
{
}
public class CheckingAccount : Account
{
private decimal _minimumCheckingBalance = 5m;
}
public class SavingsAccount : Account
{
private decimal _minimumSavingsBalance = 5m;
}
在这个例子中,两个子类中包含重复的常量。为了提高复用性我们将字段上移到基类中,并简化其名称。
public abstract class Account
{
protected decimal _minimumBalance = 5m;
}
public class CheckingAccount : Account
{
}
public class SavingsAccount : Account
{
}
本项重构从两方面减少重复:首先它去除了重复的数据声明;其次它使你可以将使用该字段的行为从子类移至父类,从而去除重复的行为。
阅读全文
2009-08-13 23:58:24
摘要:Extract class:提炼类
你也许听过类似这样的教诲:一个class应该是一个清楚的抽象,处理一些明确的责任。但是在实际工作中,class会不断成长扩展。于是随着责任不断增加,这个class会变得很复杂。很快这个class就会变成一团乱麻。建立一个新的class,将相关的字段和方法从旧class搬移到新class。
public class Person
{
public string GetName()
{
//other code
}
public void SetName(string name)
{
//other code
}
public string GetTelephoneNumber()
{
//other code
}
public string GetOfficeAreaCode()
{
//other code
}
public void SetOfficeAreaCode(string areaCode)
{
//other code
}
public string GetOfficNumber()
{
//other code
}
public void SetOfficNumber(string number)
{
//other code
}
}
这个例子中,可以将与Office相关的行为分离到一个独立的class中。
public class Person
{
public string GetName()
{
//other code
}
public void SetName(string name)
{
//other code
}
public string GetTelephoneNumber()
{
//other code
}
}
public class Office
{
public string GetOfficeAreaCode()
{
//other code
}
public void SetOfficeAreaCode(string areaCode)
{
//other code
}
public string GetOfficNumber()
……
阅读全文
2009-08-12 20:18:00
摘要:Introduce Design By Contract:契约式设计
契约式设计(DBC,Design By Contract)定义了方法应该包含输入和输出验证。因此,可以确保所有的工作都是基于可用的数据,并且所有的行为都是可预料的。否则,将返回异常或错误并在方法中进行处理。在我们的示例中,输入参数很可能为null。由于没有进行验证,该方法最终会抛出NullReferenceException。在方法最后,我们也并不确定是否为用户返回了一个有效的decimal,这可能导致在别的地方引入其他方法。
public class CashRegister
{
public decimal TotalOrder(IEnumerableProduct products, Customer customer)
{
decimal orderTotal = products.Sum(product = product.Price);
customer.Balance += orderTotal;
return orderTotal;
}
}
在此处引入DBC 验证是十分简单的。首先,我们要声明customer不能为null,并且在计算总值时至少要有一个product。在返回订单总值时,我们要确定其值是否有效。如果此例中任何一个验证失败,我们将以友好的方式抛出相应的异常来描述具体信息,而不是抛出一个晦涩的NullReferenceException。在.NET Framework 3.5的Microsoft.Contracts命名空间中包含一些DBC框架和异常。我个人还没有使用,但它们还是值得一看的。
public class CashRegister
{
public decimal TotalOrder(IEnumerableProduct products, Customer customer)
{
if (customer == null)
throw new ArgumentNullException(customer, Customer cannot be null);
if (products.Count() == 0)
throw new ArgumentException(Must have at least one ……
阅读全文
2009-08-10 10:44:41
摘要:某个函数返回的对象,需要由函数调用者执行向下转型动作,将向下转型动作移到函数中。
需要转型的代码如下:
public object LastReading()
{
return readings.lastElement();
}
将转型操作提到函数内后的代码
public Reading LastReading()
{
return (Reading)readings.lastElement();
}
如果你的某个函数返回一个值,并且你知道你所返回的对象的具体类型时,如果你返回了一个更一般的类型,你便是在函数用户身上强加了非必要的工作。这中情况下你不应该要求用户承担向下转型的责任,应该尽量为他们提供准确的型别。
阅读全文
2009-08-03 20:27:24
摘要:你希望在创建对象时不仅仅是对它做简单的建构动作,将构造函数替换为工厂函数。
public Employee(int type)
{
_type = type;
}
上面构造函数重构成工厂函数后代码如下
public static Employee Create(int type)
{
return new Employee(type);
}
private Employee(int type)
{
_type = type;
}
你可能常常需要根据type code创建相应的对象。现在,创建名单中还得加上subclasses,那么subclasses也是根据type code来创建。然后由于构造函数只能返回被索求的对象,因此你需要将构造函数替换为工厂函数。
阅读全文
2009-07-30 13:19:11
摘要:Replace Exception with Test:以测试取代异常
面对一个调用者可预先加以检查的条件,你抛出了一个异常,请修改调用者,使它在调用函数之前先做测试。
public int GetValueForPeriod(int periodNumber)
{
try
{
return _values[periodNumber];
}
catch (Exception)
{
return 0;
}
}
加入条件判断后代码
public int GetValueForPeriod(int periodNumber)
{
if(periodNumber = _values.Length)
return 0;
return _values[periodNumber];
}
异常的出现是程序语言的一大进步。异常可协助我们避免很多复杂的错误处理逻辑。但是,异常也会被滥用。异常只应该被用于异常的、罕见的行为,也就是那些产生意料之外的错误行为,而不应该成为条件检查的代替品。如果你可以合理期望调用者在调用函数之前先检查某个条件,那么你就应该提供一个测试,而调用者应该使用它。
阅读全文