65.9K
CodeProject 正在变化。 阅读更多。
Home

模板设计模式的六种常见用法:设计模式系列

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (23投票s)

2011年12月29日

CPOL

7分钟阅读

viewsIcon

117317

在本文中,我们将了解模板设计模式的六种常见用法。

目录

引言和目标

在本文中,我们将尝试理解模板设计模式的六个重要用途。模板设计模式是许多地方被有意识或无意识使用的模式之一。本文将详细介绍该模式显而易见的六个应用场景。

定义:模板模式用于我们希望在泛化和特化关系中创建可扩展行为的场景。

如果您是设计模式的新手,可以先观看我的 工厂模式视频。这是一个很老的视频了,抱歉质量不高,我刚开始录制的时候还很新手。

您也可以查看末尾的参考部分,那里有我关于设计模式的完整四部分系列。

模板模式简介

模板模式属于行为模式类别。模板模式定义了一个主流程模板,该主流程模板按顺序调用子流程。之后,可以修改主流程的子流程以生成不同的行为。

例如,下面是一个将数据解析并加载到 Oracle 的简单过程。整个过程有三个固定步骤:

  • 从源加载数据
  • 解析数据
  • 将数据转储到 Oracle

现在,您可以修改“加载”和“解析”的实现来创建一个 CSV 文件加载过程。调用“加载”、“解析”和“转储”的整体顺序将保持不变,但我们可以完全自由地更改“加载”、“解析”和“转储”的实现,从而创建新过程。

从上图可以看出,我们修改了“加载”和“解析”子流程来生成 CSV 文件和 SQL Server 加载过程。“转储”函数以及调用子流程的顺序在子流程中未改变。

为了实现模板模式,我们需要遵循四个重要步骤:

  1. 通过创建父抽象类来创建模板或主进程。
  2. 通过定义抽象方法和函数来创建子进程。
  3. 创建一个定义子进程调用顺序的方法。此方法应定义为普通方法,以便子方法无法覆盖它。
  4. 最后创建子类,这些子类可以修改抽象方法或子进程以定义新实现。
public abstract class GeneralParser
{
    protected abstract void Load();

    protected abstract void Parse();
    protected virtual void Dump()
    {
        Console.WriteLine("Dump data in to oracle");
    }
    public void Process()
    {
        Load();
        Parse();
        Dump();
    }
}

SqlServerParser 继承自 GeneralParser,并用 SQL Server 实现覆盖了 LoadParse 方法。

public class SqlServerParser : GeneralParser
{
    protected override void Load()
    {
        Console.WriteLine("Connect to SQL Server");
    }
    protected override void Parse()
    {
        Console.WriteLine("Loop through the dataset");
    }
}

FileParser 继承自 GeneralParser,并用文件特定实现覆盖了 LoadParse 方法。

public class FileParser : GeneralParser
{
    protected override void Load()
    {
        Console.WriteLine("Load the data from the file");
    }
    protected override void Parse()
    {
        Console.WriteLine("Parse the file data");
    }
  
}

现在,客户端可以调用这两个解析器。

FileParser ObjFileParser = new FileParser();
ObjFileParser.Process();
Console.WriteLine("-----------------------");
SqlServerParser ObjSqlParser = new SqlServerParser();
ObjSqlParser.Process();
Console.Read();

下面显示了这两个解析器的输出。

Load the data from the file
Parse the file data
Dump data in to oracle
-----------------------
Connect to SQL Server
Loop through the dataset
Dump data in to oracle

现在,让我们看看“模板”模式可以实现的几个实际场景。

场景 1:灵活可扩展的通用特化用户界面

很多时候我们会遇到看起来几乎相同但外观和感觉略有不同的 UI。例如,下图中的屏幕数据相同,但背景颜色不同。

在这种情况下,窗体构造函数将成为主进程,它将调用三个进程/函数:

  • InitializeComponent:这将创建窗体所需的 UI 对象。
  • LoadCustomer:此函数将加载数据并绑定到网格。
  • LoadGrid:此函数将定义网格的外观和感觉。
public Form1()
{
    InitializeComponent();
    LoadCustomer();
    LoadGrid();
}

下面是基窗体的完整代码。请注意:LoadGrid 是一个抽象方法。换句话说,我们可以创建具有不同颜色实现的窗体,而不会干扰窗体的其余部分。

public abstract partial class Form1 : Form
{
    protected Customers objCustomers = new Customers();
    protected List<customer> oCustomerList;
    public Form1()
    {
        InitializeComponent();
        LoadCustomer();
        LoadGrid();
    }

    public void LoadCustomer()
    {
        oCustomerList = objCustomers.GetCustomers();
    }
    public abstract void LoadGrid();
}

如果您想创建一个具有不同背景的新窗体,可以保持其他代码不变,只需用不同的颜色/外观覆盖 LoadGrid 功能即可。下面是示例代码:

public partial class Form3 : Form1
{
    ....
    ....
    ....
    ....
        public override void LoadGrid()
        {
            dgGridCustomer.DataSource = oCustomerList;
            dgGridCustomer.BackgroundColor = Color.Aqua;
            dgGridCustomer.ForeColor = Color.Brown;
        }
}

场景 2:ASP.NET 页面生命周期

模板模式非常明显的场景之一是 ASP.NET 页面生命周期。在 ASP.NET 页面生命周期中,生命周期顺序是固定的,但它提供了完全的权限来覆盖每个序列的实现。

例如,在 ASP.NET 页面生命周期中,我们有 Init、Load、Validate、Prerender、Render 等各种事件。顺序是固定的,但我们可以根据需要覆盖每个事件的实现。

场景 3:代码生成器

我们经常使用 T4 模板、LINQ、EF 等代码生成器从数据库表设计中生成代码。现在,代码生成器在单独的物理文件中工作,它会为您生成代码。因此,如果更改表设计,它将重新生成文件。

现在,如果您想添加一些自定义代码,则无法更改自动生成的代码文件,因为当 DB 设计更改时,您的代码将被替换。因此,最好的方法是使用单独的文件扩展生成的代码类,并将您的自定义代码放在该类中。使用模板模式可以非常有效地完成此扩展。生成的代码类可以定义一个固定流程,但同时提供空的虚拟方法、属性和函数,这些可以扩展以注入到您的自定义逻辑中。

场景 4:XML 解析器

适用于模板模式的另一个场景是 XML 解析器。在 XML 中,我们通常解析父元素和子元素。在许多场景中,解析几乎是通用的,只有子元素有一些小的变化。

例如,在下面的代码片段中,我们有 Customer 作为父元素,每个客户将有 OrdersOrders 将有 Product。现在,CustomerOrders 的解析将是相同的,但 Product 标签可以根据情况有一个 Size 属性。例如,在下面的代码片段中,Product 元素只有产品的名称和数量。

<Customer Name="Shiv">
<Orders OrderNumber="1001">
<Product Name="Shirts" Amount="1000"/>
<Product Name="Socks" Amount="100"/>
</Orders>
</Customer>

在某些情况下,您的 Product 元素可能有其他变体,如下面的 XML 代码片段所示。在这种情况下,您可以只覆盖 Product 元素的解析过程,并保持整体 XML 解析过程的顺序不变。

<Customer Name="Shiv">
<Orders OrderNumber="1001">
<Product Name="Shirts" Amount="1000">
<LargeSize/>
</Product>
<Product Name="Socks" Amount="1000">
<SmallSize/>
</Product>
</Orders>
</Customer>

场景 5:业务组件中的验证

业务类具有验证功能,我们希望创建具有不同验证逻辑的业务类版本。

public class Supplier
{
    private string _SupplierCode;

    public string SupplierCode
    {
        get 
        { 
            return _SupplierCode; 
        }
        set 
        {
            ValidateSuppCode(value);
            _SupplierCode = value; 
        }
    }

    public virtual  void ValidateSuppCode(string SuppCode)
    {
        if (SuppCode.Length == 0)
        {
            throw new Exception("Can not be null");
        }
    }
}

public class SupplierNew : Supplier
{
    public override void ValidateSuppCode(string SuppCode)
    {
        base.ValidateSuppCode(SuppCode);
        if (SuppCode.Length > 10)
        {
            throw new Exception("can not be more than 10");
        }
    }
}

场景 6:可定制的日志记录实用工具

这是模板模式非常适合的另一个场景。如果您查看这些组件,即消息记录器、错误记录器等,它们会分两个阶段执行:第一阶段准备消息,第二阶段记录消息。

对于这些类型的场景,我们可以创建一个父类,该类定义两个固定顺序:一个准备消息,另一个将消息记录到源(文件、事件查看器、电子邮件等)。

之后,我们可以创建子类,它们可以继承并覆盖这些阶段的逻辑,但保持顺序不变。

设计模式的实时可视化图

templatepatt/Visualization.jpg

设计模式参考

欢迎免费下载这些 FAQ PDF,以及我网站上的设计模式文章,我收集了约 400 个关于 SilverLight、Azure、VSTS、WCF、WPF、WWF、SharePoint、设计模式、UML 等的 FAQ 问题和答案。

进一步阅读,请观看下面的面试准备视频和分步视频系列。

© . All rights reserved.