对一个排序程序的不断重构
最近写了一个排序的程序
需求从最开始的只要能对数组进行升序排序不断的进化
最后成为以下的样子:
1 可以升序和降序排序
2 能够自由的扩充排序算法而不需改动原有程序
3 能纪录排序的比较次数、交换次数、排序用时
4 通过扩展,能够对任意对象进行排序
5 通过扩展,能够将排序过程相关的信息打印在任何位置
所以代码从最开始的一个简单的函数被重构成了
包含3个 接口、N个类的包
程序的输出入下:
**********************************************
2005-10-12 0:16:57
The original array is :
2005-01-01 6:06:06,2004-01-01 6:06:06,2006-01-01 6:06:06,2005-01-01 6:06:07
Now using BubbleSorter, direction : Descending
Sort was finished.
Sort took 40.0576 ms.
Sort compared 6 times.
Sort swapped 4 times.
The sorted array is :
2006-01-01 6:06:06,2005-01-01 6:06:07,2005-01-01 6:06:06,2004-01-01 6:06:06
**********************************************
2005-10-12 0:16:57
The original array is :
1,3,2,4,5,6.5,7,8
Now using BubbleSorter, direction : Ascending
Sort was finished.
Sort took 20.0288 ms.
Sort compared 13 times.
Sort swapped 1 times.
The sorted array is :
1,2,3,4,5,6.5,7,8
………………(其余部分省略)
起初,BubbleSorter的代码如下
/// 冒泡排序算法,使用了策略模式避免了对具体比较器的依赖
/// </summary>
public class BubbleSorter : SorterBase
{
/// <summary>
/// 构造器
/// </summary>
public BubbleSorter():this(new CommonComparer(),new ConsolePrinter())
{
}
/// <summary>
/// 构造器
/// </summary>
/// <param name="comparer">比较器实例</param>
public BubbleSorter(IComparer comparer):this(comparer,new ConsolePrinter())
{
}
/// <summary>
/// 构造器
/// </summary>
/// <param name="comparer">比较器实例</param>
/// <param name="printer">打印器实例</param>
public BubbleSorter(IComparer comparer,IPrinter printer)
{
Initialize(comparer,printer);
}
public override void Sort(Array arrayToSort,SortDirection sortDirection)
{
if(arrayToSort == null) throw new ArgumentNullException("arrayToSort");
StartSort(this,arrayToSort,sortDirection);
bool isSwap = false ;
try
{
for(int i = 0 ; i <= arrayToSort.Length - 1 ; i ++)
{
isSwap = false ;
for(int j = arrayToSort.Length-2 ; j >= i ; j --)
{
if(CompareAndSwap(arrayToSort,j,j+1))
isSwap = true;
}
if(!isSwap) break ;
}
FinishSort(arrayToSort);
}
catch(Exception ex)
{
RegisterException(ex);
throw;
}
}
}
当只有冒泡排序算法的时候,我对上面的程序感觉相当满意。
但是,在我实现插入排序的话,我发现了几个很不爽的事情
1。基本我要实现跟冒泡排序一样的3个构造方法
2。对于Sort方法 我必须作参数校验
3。为了显示排序信息,我必须在排序过程前后调用StartSort(this,arrayToSort,sortDirection)和FinishSort(arrayToSort)方法
4。在BubbleSorter类中和SortBase类中,很多方法都在传递arrayToSort这个参数
于是我做了几件事
1。用Extra Method将真正排序的那段代码提取到一个叫做DoSort()的方法中
2。将Sort方法提取到Super Class中,在Sort中调用抽象的 DoSort方法。--- 这刚好是模板模式。
3。将arrayToSort提升为域字段
4。去除了3个烦躁的构造函数,转而使用工厂进行构造
经过3步重构后,排序的算法保持得很纯洁,如下:
/// 冒泡排序算法
/// </summary>
public class BubbleSorter : SorterBase
{
protected override void DoSort()
{
bool isSwap = false ;
for(int i = 0 ; i <= arrayToSort.Length - 1 ; i ++)
{
isSwap = false ;
for(int j = arrayToSort.Length-2 ; j >= i ; j --)
{
if(CompareAndSwap(j,j+1))
isSwap = true;
}
if(!isSwap) break ;
}
}
}
插入排序的代码
/// 直接插入排序
/// </summary>
public class InsertSorter : SorterBase
{
/// <url>element://model:project::Sorter/design:view:::zm86v8buarhrn1i_v</url>
/// <url>element://model:project::Sorter/design:view:::kg8jy20srni144i_v</url>
protected override void DoSort()
{
for(int i=1;i< arrayToSort.Length;i++)
{
//依次与前面的记录进行比较,如比它小就交换
for(int j=i;j>0;j--)
{
if( !CompareAndSwap( j-1 , j) ) break;
}
}
}
}
编码采用了TDD,如果没有单元测试,重构会是一场噩梦。
对于用于测试的代码也经过了重构
最开始只有一个类用于测试冒泡排序代码
后来加入了插入排序算法以后
发现原先的测试代码没有办法重用,于是抽象出了一个测试基类TestSorterBase,这个类里面存在大量的测试方法,但是这个类没有被标记[TestFixture]
从里面派生出的两个类 TestInsertSorter & TestBubbleSorter,被标记为[TestFixture],却没有声明任何[Test]特性的方法
代码如下
/// TestInsertSort 的摘要说明。
/// </summary>
[TestFixture]
public class TestBubbleSorter : TestSortBase
{
protected override SorterBase CreateSorter()
{
return SorterFactory.GetSorter( typeof( BubbleSorter ) );
}
}
这个稍候再作详细的介绍。
呵呵 偶还在增加需求 继续重构