标题: .NET设计模式(16):模版方法(Template Method)
- TerryLee 2006-07-04 18:07 阅读:8430
- 评论:17 查看评论 | 添加评论

摘要:Template Method模式是比较简单的设计模式之一,但它却是代码复用的一项基本的技术,在类库中尤其重要。

 

主要内容

1.概述

2.Template Method解说

3..NET中的Template Method模式

4.适用性及实现要点

 

概述

变化一直以来都是软件设计的永恒话题,在XP编程中提倡拥抱变化,积极应对。如何更好的去抓住变化点,应对变化?如何更好的提高代码复用?通过学习Template Method模式,您应该有一个新的认识。

意图

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。[-GOF《设计模式》]

结构图

[图片]

图1  Template Method 模式结构图

 

生活中的例子

模板方法定义了一个操作中算法的骨架,而将一些步骤延迟到子类中。房屋建筑师在开发新项目时会使用模板方法。一个典型的规划包括一些建筑平面图,每个平面图体现了不同部分。在一个平面图中,地基、结构、上下水和走线对于每个房间都是一样的。只有在建筑的后期才开始有差别而产生了不同的房屋样式。

[图片]

图2  使用建筑图为例子的Template Method模式

Template Method模式解说

李建忠老师说过一句话,如果你只想掌握一种设计模式的话,那这个模式一定是Template Method模式。对于这个问题,我想可能是仁者见仁,智者见智,但是有一点不能否认的Template Method模式是非常简单而且几乎是无处不用,很少有人没有用过它。下面我们以一个简单的数据库查询的例子来说明Template Method模式(注意:这个例子在实际数据库开发中并没有任何实际意义,这里仅仅是为了作为示例而已)。

假如我们需要简单的读取Northwind数据库中的表的记录并显示出来。对于数据库操作,我们知道不管读取的是哪张表,它一般都应该经过如下这样的几步:

1.连接数据库(Connect)

2.执行查询命令(Select)

3.显示数据(Display)

4.断开数据库连接(Disconnect)

这些步骤是固定的,但是对于每一张具体的数据表所执行的查询却是不一样的。显然这需要一个抽象角色,给出顶级行为的实现。如下图:

[图片]

图3

Template Method模式的实现方法是从上到下,我们首先给出顶级框架DataAccessObject的实现逻辑:

[图片]public abstract class DataAccessObject
[图片]
[图片][图片][图片]{
[图片]    protected string connectionString;
[图片]
[图片]    protected DataSet dataSet;
[图片]
[图片]    public virtual void Connect()
[图片]
[图片][图片]    [图片]
[图片]        connectionString = 
[图片]
[图片]            "Server=Rj-097;User Id=sa;Password=sa;Database=Northwind";
[图片]
[图片]    }
[图片]
[图片]    public abstract void Select();
[图片]
[图片]    public abstract void Display();
[图片]
[图片]
[图片]    public virtual void Disconnect()
[图片]
[图片][图片]    [图片]{
[图片]        connectionString = "";
[图片]    }
[图片]
[图片]    // The "Template Method" 
[图片]
[图片]    public void Run()
[图片]
[图片][图片]    [图片]{
[图片]        Connect();
[图片]
[图片]        Select();
[图片]
[图片]        Display();
[图片]
[图片]        Disconnect();
[图片]    }
[图片]}

显然在这个顶级的框架DataAccessObject中给出了固定的轮廓,方法Run()便是模版方法,Template Method模式也由此而得名。而对于Select()和Display()这两个抽象方法则留给具体的子类去实现,如下图:

[图片]

图4

示意性实现代码:

[图片]class Categories : DataAccessObject
[图片]
[图片][图片][图片]{
[图片]    public override void Select()
[图片][图片]    [图片]{
[图片]        string sql = "select CategoryName from Categories";
[图片]
[图片]        SqlDataAdapter dataAdapter = new SqlDataAdapter(
[图片]
[图片]            sql, connectionString);
[图片]
[图片]        dataSet = new DataSet();
[图片]
[图片]        dataAdapter.Fill(dataSet, "Categories");
[图片]
[图片]    }
[图片]
[图片]    public override void Display()
[图片]
[图片][图片]    [图片]{
[图片]
[图片]        Console.WriteLine("Categories ---- ");
[图片]
[图片]        DataTable dataTable = dataSet.Tables["Categories"];
[图片]
[图片]        foreach (DataRow row in dataTable.Rows)
[图片]
[图片][图片]        [图片]{
[图片]
[图片]            Console.WriteLine(row["CategoryName"].ToString());
[图片]
[图片]        }
[图片]
[图片]        Console.WriteLine();
[图片]
[图片]    }
[图片]}

 

[图片]class Products : DataAccessObject
[图片]
[图片][图片][图片]{
[图片]    public override void Select()
[图片]
[图片][图片]    [图片]{
[图片]        string sql = "select top 10 ProductName from Products";
[图片]
[图片]        SqlDataAdapter dataAdapter = new SqlDataAdapter(
[图片]
[图片]            sql, connectionString);
[图片]
[图片]        dataSet = new DataSet();
[图片]
[图片]        dataAdapter.Fill(dataSet, "Products");
[图片]
[图片]    }
[图片]
[图片]    public override void Display()
[图片]
[图片][图片]    [图片]{
[图片]
[图片]        Console.WriteLine("Products ---- ");
[图片]
[图片]        DataTable dataTable = dataSet.Tables["Products"];
[图片]
[图片]        foreach (DataRow row in dataTable.Rows)
[图片]
[图片][图片]        [图片]{
[图片]            Console.WriteLine(row["ProductName"].ToString());
[图片]
[图片]        }
[图片]
[图片]        Console.WriteLine();
[图片]
[图片]    }
[图片]
[图片]}

再来看看客户端程序的调用,不需要再去调用每一个步骤的方法:

[图片]public class App
[图片]
[图片][图片][图片]{
[图片]    static void Main()
[图片][图片]    [图片]{
[图片]
[图片]        DataAccessObject dao;
[图片]
[图片]
[图片]        dao = new Categories();
[图片]
[图片]        dao.Run();
[图片]
[图片]
[图片]        dao = new Products();
[图片]
[图片]        dao.Run();
[图片]
[图片]        // Wait for user 
[图片]
[图片]        Console.Read();
[图片]
[图片]    }
[图片]
[图片]}

在上面的例子中,需要注意的是:

1.对于Connect()和Disconnect()方法实现为了virtual,而Select()和Display()方法则为abstract,这是因为如果这个方法有默认的实现,则实现为virtual,否则为abstract。

2.Run()方法作为一个模版方法,它的一个重要特征是:在基类里定义,而且不能够被派生类更改。有时候它是私有方法(private method),但实际上它经常被声明为protected。它通过调用其它的基类方法(覆写过的)来工作,但它经常是作为初始化过程的一部分被调用的,这样就没必要让客户端程序员能够直接调用它了。

3.在一开始我们提到了不管读的是哪张数据表,它们都有共同的操作步骤,即共同点。因此可以说Template Method模式的一个特征就是剥离共同点。

.NET 中的Template Method模式

.NET Framework中Template Method模式的使用可以说是无处不在,比如说我们需要自定义一个文本控件,会让它继承于RichTextBox,并重写其中部分事件,如下例所示:

[图片]public class MyRichTextBox : RichTextBox
[图片]
[图片][图片][图片]{
[图片]
[图片]    private static bool m_bPaint = true;
[图片]
[图片]    private string m_strLine = "";
[图片]
[图片]    private int m_nContentLength = 0;
[图片]
[图片]    private int m_nLineLength = 0;
[图片]
[图片]    private int m_nLineStart = 0;
[图片]
[图片]    private int m_nLineEnd = 0;
[图片]
[图片]    private string m_strKeywords = "";
[图片]
[图片]    private int m_nCurSelection = 0;
[图片]
[图片]
[图片]    protected override void OnSelectionChanged(EventArgs e)
[图片]
[图片][图片]    [图片]{
[图片]        m_nContentLength = this.TextLength;
[图片]
[图片]        int nCurrentSelectionStart = SelectionStart;
[图片]
[图片]        int nCurrentSelectionLength = SelectionLength;
[图片]
[图片]        m_bPaint = false;
[图片]
[图片]        m_nLineStart = nCurrentSelectionStart;
[图片]
[图片]        while ((m_nLineStart > 0) && (Text[m_nLineStart - 1] != ',')&& (Text[m_nLineStart - 1] != '{')&& (Text[m_nLineStart - 1] != '('))
[图片]
[图片]            m_nLineStart--;
[图片]
[图片]        m_nLineEnd = nCurrentSelectionStart;
[图片]
[图片]        while ((m_nLineEnd < Text.Length) && (Text[m_nLineEnd] != ',')&& (Text[m_nLineEnd] != '}')&& (Text[m_nLineEnd] != ')')&& (Text[m_nLineEnd] != '{'))
[图片]
[图片]            m_nLineEnd++;
[图片]
[图片]
[图片]        m_nLineLength = m_nLineEnd - m_nLineStart;
[图片]
[图片]        m_strLine = Text.Substring(m_nLineStart, m_nLineLength);
[图片]
[图片] 
查看评论 | 添加评论
返回顶部 | 返回首页