一个软件项目最怕的是需求的变更,然而在此过程中唯一不变的只有变化——需求不断地变化——只是发生的时间或早或晚,这对矛盾难以调和,我们力所能及的只能是不停地修改自己辛辛苦苦修改调试,再修改调试,继续修改调试后的代码,但是为了能够在修改和调试的时候能够省下更多的时间和精力来享受生活或制造更多需要我们修改调试的新代码行,学习设计模式是不错的选择。懒惰的人更适合学习这些经过无数人实践证明能够更好地应对变更的编程方式,当懒惰的人能够熟练运用的时候,他就可以有更多的时间懒惰或者将懒惰编程一种美德。
学习设计模式并不是将GOF那本经典之作看完或背下来就够了,没有经过实践的知识总是不能完全掌握的,不同具体项目中,设计上的相同需求总是以不同的面目出现的,要想练就一双火眼金睛,唯一的办法就是实践。因此对于书中的每个模式,理论上讲能够在理解的情况下于新的场景中再现这种模式,并不断地思考这些模式的优缺点和相互之间的异同,学习的效果将会非常可观。
既然是学习设计模式,就得搭建一个测试各种模式的平台,对于每一个模式去创建一个工程,略显麻烦,把模式分隔开来学习也是不应该的,通过把对每个模式的测试代码集合到一个平台上,可以让学习者更好地理解《设计模式》一书中的许多行话。话不多说就用设计模式来实现这样一个测试平台吧。
GOF的这本书里一共介绍了23种模式,所以这个平台至少要能够测试这23个模式,实际上再加上一个算不上真正的模式的常用模式——简单工厂——和一个代表除了这24种模式之外的模式的模式,一共是25个模式。对于这一变化,简单工厂可以较好的解决。即先建立一个PatternTester抽象类,然后再根据当前需要测试的模式编写相应的PatternTester子类,例如StrategyPatternTester,ProxyPatternTester等等,最后利用简单工厂产生需要的测试类实例,这样可以达到使测试程序Main函数(客户端)变化变小。
所谓简单工厂,就是利用一个专门负责生成实例对象的工厂类,暴露一个产生实例对象的够以根据用户的需要返回相应的实例对象。具体如下:
public abstract class PatternTester
{
}
//使用继承和多态统一简单工厂类接口签名
public class StrategyPatternTester : PatternTester
{
}
public class ProxyPatternTester : PatternTester
{
}
public class NotInBookPatternTester : PatternTester
{
}
Stragegy(策略)与Proxy(代理)均为模式名。
public class PatternTesterSimpleFactory
{
//如果上面没有使用继承,不能将接口的返回值类型统一为PatternTester
public staticPatternTester CreatePatternTester(string patternType)
{
switch(patternType)
{
case “Strategy”: returnnew StrategyPatternTester();
case “Proxy”: returnnew ProxyPatternTester();
default: returnnew NotInBookPatternTester();
}
}
}
这样,就可以在客户端这样生成相应的测试类:
static void Main(string[]args)
{
string patternType = “Strategy”;
//利用多态,tester在运行时知道应该调用哪个子类的成员方法
//并且避免了在客户端的代码中显示调用new运算符,降低了变更
//例如写成:PatternTester tester = new StrategyPatternTester();当要测试的模式
//改为Proxy模式时,这一整行代码要改为:
//PatternTester tester = new StrategyPatternTester();
//后面我们可以看到string patternType = “Strategy”;这行代码也可以消去,这样就
//可以使客户端代码完全稳定下来
PatternTester tester =PatternTesterSimpleFactory.CreatePatternTester(patternType);
}
实际上,对于每一种模式,都必须以某种方式展现出模式运行的机制,通过控制台打印程序运行结果,据此跟踪运行路径是不错的选择,但是有的模式以GUI的形式呈现结果会更直观。由于任何一个程序都由三部分组成——输入,计算,输出——所以在对于可以在PatternTester抽象类中对测试过程分解为三步:RequireInput,RunTest,ShowOutput。这是三个虚函数,留给每个PatternTester子类根据所需改写,为了客户端简单和隐藏测试程序执行细节,这三个方式都为保护级别(protected)方法,而使用一个公开Run方法封装测试程序执行的步骤,这很适合使用模板方法模式。
所谓模板方法,是对比较稳定的过程在高层进行封装,但是把具体的执行过程开放给子类来重写,具体代码如下:
public abstract class PatternTester
{
//这是三个执行步骤,这里他们的缺省实现是什么也不做
//注意他们是保护级别虚函数,子类可以覆写和调用,但是
//在继承体系之外不能访问这些方法
protected virtual voidRequireInput()
{
}
protected virtual void RunTest()
{
}
protected virtual void ShowOutput()
{
}
//模板方法,封装了三个比较稳定的过程,public级别非虚函数
//在继承体系之外可以访问该方法
public void Run()
{
//由于多态,程序运行时未必调用的就是上面三个方法,
//具体被调用的函数由PatternTester对象的运行时类型
//决定
RequireInput();
RunTest();
ShowOutput();
}
}
接下来,PatternTester的子类不再覆写Run方法,只需根据需要覆写三个保护级别的虚函数中的0个或多个。
//使用继承和多态统一简单工厂类接口签名
public class StrategyPatternTester : PatternTester
{
protected override voidRequireInput()
{
// StrategyTesterForm是一个继承自Form窗口类,可以接受用户输入
//然后运行计算代码并显示结果,所以这个子类不再改写另外两个虚函数了
StrategyTesterForminputForm = new StrategyTesterForm ();
inputForm.ShowDialog();
}
}
public class ProxyPatternTester : PatternTester
{
//测试代理模式不需要输入,所以他不再改写RequireInput()
//但是
protected override voidRunTest()
{
//代理模式运行代码..
}}
public class NotInBookPatternTester : PatternTester
{
//如果这个子类被实例化,说明客户端输入的patternType参数目前无效
//可能指示了一个24种模式之外的一个模式,或是指示了一个还没有实
//现的测试模式,或者是没有意义的输入,所以只需提醒用户就可以了
public override void RequireInput()
{
Console.WriteLine(“没有找到该模式的测试程序哦!不需要输入!”);
}
public override void RunTest()
{
Console.WriteLine(“没有找到该模式的测试程序哦!NotInBookPatternTester实例正在运行”);
}
public override void ShowOutput()
{
Console.WriteLine(“没有找到该模式的测试程序哦!没有其他输出!”);
}
}
此时,客户端 Main() 函数中代码不变,只要改变一下 patterType 的指,就可以看到不同的结果啦,这说明简单工厂和模板方法通过继承和多态正在起作用。并且随着编写各种模式的加入,这些编写代码的好处会越来越彰显。但是当要测试不同的模式时,还是要去修改 patternType 的值,这多少有些不舒服。本文的下篇将去除这个变化和简单工厂中的 switch 语句,说明这个测试平台中单例模式的使用,同时还会加入反射技术、配置文件以及一个可以自动生成代码的工具,让测试更简单,把主要的精力都放到模式的实现上来。