zoukankan      html  css  js  c++  java
  • 重构初体验

    设计大师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,不过对于对付大多数问题,也足以所向披靡了。

  • 相关阅读:
    线段树
    哈希,hash
    单调栈
    树的重心
    背包问题
    最小生成树
    二分图匹配
    题解 P6355 [COCI2007-2008#3] DEJAVU
    题解 P6745 『MdOI R3』Number
    题解 P2080 增进感情
  • 原文地址:https://www.cnblogs.com/wayfarer/p/43111.html
Copyright © 2011-2022 走看看