我们在编写窗体应用的时候,时常会使用到TreeView控件来实现功能选择功能,样例如下:

 

要实现点击某个功能节点以执行相关功能的话,最直线的方式是注册TreeView控件的AfterSelect事件,然后在方法体内用switch...catch...语句处理各种功能。

这样的话,代码实现出来的效果大概是这样的:

 

代码
 1 private void trv_Functions_AfterSelect(object sender, TreeViewEventArgs e)
 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文命名吧):

 

 

代码
/// <summary>
/// 用来绑定字符串和实现了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接口和各个策略类的代码如下:

 

代码
#region 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日

posted on 2010-01-24 05:11  永恒的bluebird  阅读(903)  评论(1编辑  收藏  举报