我们在编写窗体应用的时候,时常会使用到TreeView控件来实现功能选择功能,样例如下:
要实现点击某个功能节点以执行相关功能的话,最直线的方式是注册TreeView控件的AfterSelect事件,然后在方法体内用switch...catch...语句处理各种功能。
这样的话,代码实现出来的效果大概是这样的:
2 {
3 //具体的功能结点的深度均为2
4 if (e.Node.Level == 2)
5 {
6 //检查输入,对每种情况进行相关处理
7 switch (e.Node.Text)
8 {
9 case "查找所有":
10 MessageBox.Show("查找所有");
11 //调用相关处理方法
12 break;
13 case "本月过生日":
14 MessageBox.Show("本月过生日");
15 //调用相关处理方法
16 break;
17 case "从今日起7天内过生日":
18 MessageBox.Show("从今日起7天内过生日");
19 //调用相关处理方法
20 break;
21 case "增加新条目":
22 MessageBox.Show("增加新条目");
23 //调用相关处理方法
24 break;
25 default:
26 break;
27 }
28 }
29 }
那么以后如果要添加一个新功能,就必须要找到此实现所在的源代码文件,修改方法体后重新编译。这样的话可能会带来两个问题(参考《大话设计模式》):
1、随着功能的增加,方法体内部的实现将会越来越长越来越面条式,积累下来给修改造成了障碍;
2、如果是团队开发,那么处理增加需求功能的人员将会看到所有功能的执行入口代码,而这是危险的,因为你不能限制该人员有意或者无意地改动了其它功能相关的实现,而这可能会带来损失。
造成这些问题的根源在于,这种实现方式其实违背了开放-封闭原则(尽量使代码的实现对修改封闭,而对扩展开放)。
为了解决这些问题,我们可以结合策略模式(Strategy)来对这段代码进行重构~
首先,我们给出策略模式的定义(引自《Head First 设计模式》)
策略模式的类图如下(引自吕震宇老师的Blog):
具体关于策略模式是什么,由于本文的性质就不赘述了,强烈推荐对此不了解的同学去http://www.cnblogs.com/zhenyulu/articles/82017.html一探究竟。吕震宇老师的讲解很精彩~
在现在的例子下,我们考虑后很容易发现每个case子句所做的主要工作其实都可以归结为:根据点选的功能节点的Text属性的内容选择适当的方法来执行~
那么我们就完全可以利用OO语言中多态的特性来对我们的代码进行重构了:定义接口IHandle,在其中声明一个Excute方法(其实这里不用拘泥于形式,譬如说我们还可以把Excute分成三阶段,或者说根据需要给Excute传入相关参数等等,总之要根据实际情况灵活考虑设计),然后根据功能,定义和各个已有功能相关联的实现类,它们均继承了IHandle接口。以后在TreeView的AfterSelect事件中利用多态调用IHandle实例(实际类型是相应的策略类)的Excute方法就可以解决问题了。
当然这里还有一个问题是不能回避的:我们如何根据已选节点的Text选择相应的策略类?
解决的方法不止一种,比如说使用工厂系列的设计模式,或者简单地使用字典方法(Hash表),当然,还有可以根本解决问题的依赖注入的方式等等。
这里我们使用字典方法,这比较简单而且以后也很容易用依赖注入的方式来改进。
分析完毕,然后我们就可以将switch...catch...中的代码改成如下清爽的格式了(嗯……大家原谅我蹩脚的E文命名吧):
/// 用来绑定字符串和实现了IHandle接口的策略类的字典
/// </summary>
private Dictionary<string, IHandle> FunctionHandlesDic =
new Dictionary<string, IHandle>
{
{"查找所有", new SelectAll()},
{"本月过生日", new SelectThisMonth()},
{"从今日起7天内过生日", new SelectLatestSevenDays()},
{"增加新条目", new InsertPersonInfo()}
};
private void trv_Functions_AfterSelect(object sender, TreeViewEventArgs e)
{
if (e.Node.Level == 2)
{
IHandle FunHandle = FunctionHandlesDic[e.Node.Text];
FunHandle.Excute();
}
}
定义IHandle接口和各个策略类的代码如下:
public interface IHandle
{
void Excute();
}
#endregion
#region 各个策略类的实现
public class SelectAll : IHandle
{
#region IHandle 成员
public void Excute()
{
MessageBox.Show("查找所有");
}
#endregion
}
public class SelectThisMonth : IHandle
{
#region IHandle 成员
public void Excute()
{
MessageBox.Show("本月过生日");
}
#endregion
}
public class SelectLatestSevenDays : IHandle
{
#region IHandle 成员
public void Excute()
{
MessageBox.Show("从今日起7天内过生日");
}
#endregion
}
public class InsertPersonInfo : IHandle
{
#region IHandle 成员
public void Excute()
{
MessageBox.Show("增加新条目");
}
#endregion
}
#endregion
好的~至此我们完成了对原有实现的重构,成功地应用了策略模式将TreeView的AfterSelect事件的实现方法与各个特定的功能实现的耦合降低~
我们只要加入新的实现文件,在里面定义新的策略类(继承并实现接口IHandle),然后在字典FunctionHandlesDic里添加新的绑定,并将这两个文件重新编译,即可完成增加功能的需求~是不是很方便呢?
Over~
参考的书籍和文献:
1、《大话设计模式》 程杰 著
2、《Head First 设计模式》 Eric Freeman & Elisabeth Freeman with KathySierra & Bert Bates 著
3、http://www.cnblogs.com/zhenyulu/articles/82017.html 吕震宇老师的博客
文:Weatherpop
2010年1月24日