重构初体验

设计大师Martin Fowler 在《重构——改善既有代码的设计》一书中,以其精妙的概括能力,彻底对重构技术作了全方位的总结。该书既具备大百科全书般提纲挈领的重构大纲,同时更通过实例展现了在软件设计中重构的魅力。

有感于重构艺术予我的震撼,我逐渐尝试在项目设计中开始重构之旅。在这个旅程中,存在尝试的犹豫和领悟的感动,然而最终却令我折服。如今,我希望能通过一个实际的例子,让读者也能初次体验重构的魅力。举例来说,我打算作一个容器,用来存放每个整数的阶乘
结果。最初的设计是这样:
public class FactorialContainer
{
       public FactorialContainer()
       {
              factorialList = new ArrayList();
       }

       public FactorialContainer(int capacity)
       {
              capa = capacity;
              factorialList = new ArrayList(capacity);
       }

       private ArrayList factorialList;
       private int capa;

       public ArrayList FacotorialList
       {
              get {return factorialList;}
              set {factorialList = value;}
       }
       public int Capacity
       {
              get {return capa;}
              set {capa = value;}
       }
       public long this[int index]
       {
              get {return (long)factorialList[index];}
              set {factorialList[index] = value;}
       }
       public void Add()
       {
              long result = 1;
              int seed = factorialList.Count + 1;
              for (int i=1;i< =seed;i++)
              {
                     result*=i;
              }
              factorialList.Add(result);
       }         
       public void Clear()
       {
              factorialList.Clear();
       }
       public void RemoveAt(int index)
       {
              factorialList.RemoveAt(index);
       }
}

熟悉重构的人是否已经嗅到了代码的坏味道了呢?是的,在Add()方法里,将计算阶乘的算法也放到了里面。由于这些代码实现了独立的算法,因此应该利用Extract Method规则,将这些代码提炼出来,形成独立的方法:
public void Add()
{
       long result = CountFactorial();           
       factorialList.Add(result);
}           
private long CountFactorial()
{
       long result = 1;
       int seed = factorialList.Count + 1;
       for (int i=1;i<=seed;i++)
       {
              result*=i;
       }
       return result;
}

我们还可以进一步简化Add()方法:
public void Add()
{
       factorialList.Add(Count());
}

现在我希望扩充这个容器的功能,加入菲波那契数列的计算。由于两者的计算方式是完全不同的,因此需要重新创建一个菲波那契容器:
public class FibonacciContainer
{
       public FibonacciContainer()
       {
              fibonacciList = new ArrayList();
       }
       public FibonacciContainer(int capacity)
       {
              capa = capacity;   
              fibonacciList = new ArrayList();
       }

       private ArrayList fibonacciList;
       private int capa;

       public ArrayList FibonacciList
       {
              get {return fibonacciList;}
              set {fibonacciList = value;}
       }
       public int Capacity
       {
              get {return capa;}
              set {capa = value;}
       }
       public long this[int index]
       {
              get {return (long)fibonacciList[index];}
              set {fibonacciList[index] = value;}
       }
       public void Add()
       {
              fibonacciList.Add(CountFibonacci ());
       }
       public void RemoveAt(int index)
       {
              fibonacciList.RemoveAt(index);
       }
       public void Clear()
       {
              fibonacciList.Clear();
       }
       private long CountFibonacci ()
       {
              long result = 0;
              int seed = fibonacciList.Count;
              if (seed == 0 || seed == 1)
              {
                     result = 1;
              }
              else
              {
                     result = this[seed-1] + this[seed-2];
              }
              return result;
       }
}

比较上面两段容器的代码,会有很多相似之处。又是时候拿起重构的利器了。首先我们根据name Method规则,将计算阶乘和菲波那契数列的方法改名为统一的名字。为什么要改名呢?既然两个容器有着相似之处,为什么不能定义一个基类,然后从其派生出各自的类呢?为了保证类方法的一致性,当然有必要对方法重新命名了。实际上,我们不仅要重命名方法名,而且还要改变属性的名字。

FacotorialList 、FibonacciList:改名为MathList;
CountFactorial()、CountFibonacci():改名为Count();

然后再通过Extract Class和Extract SubClass规则抽象出基类MathClass。

最后的代码为:
基类:MathContainer
public abstract class MathContainer
{
       public MathContainer()
       {
              mathList = new ArrayList();
       }      
       public MathContainer(int capacity)
       {
              capa = capacity;
              mathList = new ArrayList(capacity);
       }
       private ArrayList mathList;
       private int capa;
       public ArrayList MathList
       {
              get {return mathList;}
              set {mathList = value;}
       }
       public int Capacity
       {
              get {return capa;}
              set {capa = value;}
       }
       public long this[int index]
       {
              get {return (long)mathList[index];}
              set {mathList[index] = value;}
       }
       public void Add()
       {
              mathList.Add(Count());
       }
       public void RemoveAt(int index)
       {
              mathList.RemoveAt(index);
       }
       public void Clear()
       {
              mathList.Clear();
       }
       protected abstract long Count();     
}

然后从基类分别派生出计算阶乘和菲波那契数列的容器类:
派生类:FactorialContainer
public class FactorialContainer:MathContainer
{
       public FactorialContainer():base(){}
       public FactorialContainer(int capacity):base(capacity){}   
       protected override long Count()
       {
              long result = 1;
              int seed = MathList.Count + 1;
              for (int i=1;i<=seed;i++)
              {
                     result*=i;
              }
              return result;
       }
}

派生类:FibonacciContainer
public class FibonacciContainer:MathContainer
{
       public FibonacciContainer():base(){}
       public FibonacciContainer(int capacity):base(capacity){}
       protected override long Count()
       {
              long result = 0;
              int seed = MathList.Count;
              if (seed == 0 || seed == 1)
              {
                     result = 1;
              }
              else
              {
                     result = this[seed-1] + this[seed-2];
              }
              return result;
       }
}

UML类图如下:

对于这样的程序结构,要扩展起来是很容易的,例如素数的容器,我们只需要定义PrimeNumberContainer类,然后重写Count()方法,并派生MathContainer类即可。
 
经过重构,程序的结构变得愈发清晰。如果我们再仔细分析现在的结构,对于算法的扩展是非常容易的,但如何创建每个容器的实例,似乎还有更好的方法,那就是通过工厂来管理每个实例的创建。因为产品只有一类,所以可以参用工厂方法模式(Factory Method)。首先我们来看看UML类图:

实现代码如下:
基类工厂:MathFacotry类
public abstract class MathFactory
{
       public abstract MathContainer CreateInstance();
       public abstract MathContainer CreateInstance(int capacity);
}

阶乘容器工厂:FactorialFactory
public class FactorialFactory:MathFactory
{
       public override MathContainer CreateInstance()
       {
              return new FactorialContainer();
       }
       public override MathContainer CreateInstance(int capacity)
       {
              return new FactorialContainer(capacity);
       }
}

菲波那契数列容器工厂:
public class FibonacciFactory:MathFactory
{
       public override MathContainer CreateInstance()
       {
              return new FibonacciContainer();
       }
       public override MathContainer CreateInstance(int capacity)
       {
              return new FibonacciContainer(capacity);
       }
}

有了工厂,就可以通过工厂来创建我们所需要的具体容器类对象了:
[STAThread]
static void Main(string[] args)
{
       MathFactory factory1 = new FactorialFactory();
       MathFactory factory2 = new FibonacciFactory();

       MathContainer factorial = factory1.CreateInstance();
       MathContainer fibonacci = factory2.CreateInstance();

       Console.WriteLine("Count Factorial form 1 to 8:");
       for (int i=1;i<=8;i++)
       {
              factorial.Add();
       }
       for (int i=0;i&lt;8;i++)
       {
              Console.WriteLine(factorial[i].ToString());
       }

       Console.WriteLine();
       Console.WriteLine("Count Fibonacci form 1 to 8:");

       for (int i=1;i<=8;i++)
       {
              fibonacci.Add();
       }
       for (int i=0;i&lt;8;i++)
       {
              Console.WriteLine(fibonacci[i].ToString());
       }
       Console.ReadLine();
}

本来是一个简单的例子,似乎到后来越来越复杂了。然后仔细分析程序结构,你会发现这个的扩充性和灵活性是很好的。通过重构,并运用设计模式的工厂模式,我们逐步创建了这样一个渐趋完美的数学运算容器。大家可以试试为这个容器添加其他算法。也许在这个过程中你会发现结构还存在一些不足,那么不用担心,运用重构的武器吧。虽然这个武器可能比不上CS高手手里的AK47,不过对于对付大多数问题,也足以所向披靡了。

posted @ 2004-09-14 20:36  张逸  阅读(2945)  评论(8编辑  收藏  举报