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