Spiga

标签为重构的文章

一天一个重构方法(29):移除工具类

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…… 阅读全文

一天一个重构方法(28):下移方法

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 { } 阅读全文

一天一个重构方法(27):下移字段

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 { } 阅读全文

一天一个重构方法(26):方法上移

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 } 阅读全文

一天一个重构方法(25):字段上移

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 { } 本项重构从两方面减少重复:首先它去除了重复的数据声明;其次它使你可以将使用该字段的行为从子类移至父类,从而去除重复的行为。 阅读全文

一天一个重构方法(24):提炼类

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() …… 阅读全文

一天一个重构方法(23):契约式设计

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 …… 阅读全文

一天一个重构方法(22):封装向下转型

2009-08-10 10:44:41

摘要:某个函数返回的对象,需要由函数调用者执行向下转型动作,将向下转型动作移到函数中。 需要转型的代码如下: public object LastReading() { return readings.lastElement(); } 将转型操作提到函数内后的代码 public Reading LastReading() { return (Reading)readings.lastElement(); } 如果你的某个函数返回一个值,并且你知道你所返回的对象的具体类型时,如果你返回了一个更一般的类型,你便是在函数用户身上强加了非必要的工作。这中情况下你不应该要求用户承担向下转型的责任,应该尽量为他们提供准确的型别。 阅读全文

一天一个重构方法(21):将构造函数替换为工厂函数

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来创建。然后由于构造函数只能返回被索求的对象,因此你需要将构造函数替换为工厂函数。 阅读全文

一天一个重构方法(20):以测试取代异常

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]; } 异常的出现是程序语言的一大进步。异常可协助我们避免很多复杂的错误处理逻辑。但是,异常也会被滥用。异常只应该被用于异常的、罕见的行为,也就是那些产生意料之外的错误行为,而不应该成为条件检查的代替品。如果你可以合理期望调用者在调用函数之前先检查某个条件,那么你就应该提供一个测试,而调用者应该使用它。 阅读全文