Fork me on GitHub

重构手法之处理概括关系【4】

返回总目录

10 Form Template Method(塑造模板函数)

概要

你有一些子类,其中相应的某些函数以相同的顺序执行类似的操作,但各个操作的细节不同。

将这些操作分别放进独立的函数中,并保持它们都有相同的签名,于是原函数也就变得相同了,然后将原函数上移至基类。

动机

继承是避免重复行为的一个强大工具。无论何时,只要你看见两个子类之中有类似的函数,就可以把它们提升到基类。但是如果这些函数并不完全相同该这么办?仍有必要尽量避免重复,但又必须保持这些函数之间的实质差异。

常见的一种情况是:两个函数以相同的顺序执行大致相近的操作,但是各操作不完全相同。这种情况下我们可以将执行的序列移至基类,并借助多态保证各操作仍得以保持差异性。这样的函数被称为Template Method(模板函数)。

范例

Customer类中有两个用于打印的函数。Statment()函数用于ASCII码打印报表,HtmlStatement()函数则以HTML格式输出报表:

class Customer
{
    public string Name { get; }

    private List<Rental> _rentals = new List<Rental>();

    public List<Rental> GetRentals()
    {
        return _rentals;
    }
    public Customer(string name)
    {
        Name = name;
    }
    public string Statement()
    {
        string result = "Rental Record for " + Name + "\n";

        foreach (var rental in _rentals)
        {
            result += "\t" + rental.Title + "\t" + rental.GetCharge().ToString() + "\n";
        }
        //add footer lines
        result += "Amount owed is " + GetTotalCharge() + "\n";
        result += "You earned " + GetTotalFrequentRenterPoints() + " frequent renter points";
        return result;
    }
    public string HtmlStatement()
    {
        string result = "<h1>Rentals for <em>" + Name + "</em></h1>\n";

        foreach (var rental in _rentals)
        {
            result += rental.Title + ": " + rental.GetCharge().ToString() + "<br/>\n";
        }
        //add footer lines
        result += "<p>You owe  <em>" + GetTotalCharge() + "<em/></p>\n";
        result += "On this rental you earned <em>" + GetTotalFrequentRenterPoints() + "</em> frequent renter points";
        return result;
    }
    public double GetTotalCharge()
    {
        return _rentals.Sum(rental => rental.GetCharge());
    }

    public int GetTotalFrequentRenterPoints()
    {
        return _rentals.Sum(rental => rental.GetFrequentRenterPoints());
    }
}

class Rental
{
    public string Title { get; set; }
    public double GetCharge()
    {
        return 1.5;
    }
    public int GetFrequentRenterPoints()
    {
        return 3;
    }
}

使用Form Template Method之前,需要对上述两个函数做一些整理,使它们成为同一个基类下的子类函数。为了这一目的,使用函数对象针对“报表打印”创建一个独立的策略继承体系:

class Statement{}
class TextStatement : Statement{}
class HtmlStatement : Statement{}

现在,通过Move Method,将两个负责输出报表的函数分别搬移到对应的子类中:

class Customer
{
    public string Name { get; }

    private List<Rental> _rentals = new List<Rental>();

    public List<Rental> GetRentals()
    {
        return _rentals;
    }
    public Customer(string name)
    {
        Name = name;
    }
    /// <summary>
    /// 以ASCII码打印报表
    /// </summary>
    /// <returns></returns>
    public string Statement()
    {
        return new TextStatement().Value(this);
    }
    /// <summary>
    /// 以HTML格式打印报表
    /// </summary>
    /// <returns></returns>
    public string HtmlStatement()
    {
        return new HtmlStatement().Value(this);
    }
    public double GetTotalCharge()
    {
        return _rentals.Sum(rental => rental.GetCharge());
    }

    public int GetTotalFrequentRenterPoints()
    {
        return _rentals.Sum(rental => rental.GetFrequentRenterPoints());
    }
}
class Statement
{

}

class TextStatement : Statement
{
    /// <summary>
    /// 以ASCII码打印报表
    /// </summary>
    /// <returns></returns>
    public string Value(Customer customer)
    {
        string result = "Rental Record for " + customer.Name + "\n";
        var rentals = customer.GetRentals();
        foreach (var rental in rentals)
        {
            result += "\t" + rental.Title + "\t" + rental.GetCharge().ToString() + "\n";
        }
        //add footer lines
        result += "Amount owed is " + customer.GetTotalCharge() + "\n";
        result += "You earned " + customer.GetTotalFrequentRenterPoints() + " frequent renter points";
        return result;
    }
}

class HtmlStatement : Statement
{
    /// <summary>
    /// 以HTML格式打印报表
    /// </summary>
    /// <returns></returns>
    public string Value(Customer customer)
    {
        string result = "<h1>Rental Record for <em>" + customer.Name + "</em></h1>\n";
        var rentals = customer.GetRentals();
        foreach (var rental in rentals)
        {
            result += rental.Title + ": " + rental.GetCharge().ToString() + "<br/>\n";
        }
        //add footer lines
        result += "<p>You owe  <em>" + customer.GetTotalCharge() + "<em/></p>\n";
        result += "On this rental you earned <em>" + customer.GetTotalFrequentRenterPoints() + "</em> frequent renter points";
        return result;
    }
}

class Rental
{
    public string Title { get; set; }
    public double GetCharge()
    {
        return 1.5;
    }
    public int GetFrequentRenterPoints()
    {
        return 3;
    }
}

面对两个子类中的相似函数,我可以开始实施Form Template Method了。本重构的关键在于:运用Extract Method将两个函数的不同部分提炼出来,从而将相似的代码和变动的代码分开。每次提炼后,就建立一个签名相同但本体不同的函数。

class TextStatement : Statement
{

    public string HeaderString(Customer customer)
    {
        return "Rental Record for " + customer.Name + "\n";
    }

    public string EachRentalsString(Rental rental)
    {
        return "\t" + rental.Title + "\t" + rental.GetCharge().ToString() + "\n";
    }

    public string FooterString(Customer customer)
    {
        return "Amount owed is " + customer.GetTotalCharge() + "\n" +
         "You earned " + customer.GetTotalFrequentRenterPoints() + " frequent renter points";
    }
    /// <summary>
    /// 以ASCII码打印报表
    /// </summary>
    /// <returns></returns>
    public string Value(Customer customer)
    {
        string result = HeaderString(customer);
        var rentals = customer.GetRentals();
        foreach (var rental in rentals)
        {
            result += EachRentalsString(rental);
        }
        //add footer lines
        result += FooterString(customer);
        return result;
    }
}

class HtmlStatement : Statement
{

    public string HeaderString(Customer customer)
    {
        return "<h1>Rental Record for <em>" + customer.Name + "</em></h1>\n";
    }

    public string EachRentalsString(Rental rental)
    {
        return rental.Title + ": " + rental.GetCharge().ToString() + "<br/>\n";
    }

    public string FooterString(Customer customer)
    {
        return "<p>You owe  <em>" + customer.GetTotalCharge() + "<em/></p>\n" +
               "On this rental you earned <em>" + customer.GetTotalFrequentRenterPoints() + "</em> frequent renter points";
    }
    /// <summary>
    /// 以HTML格式打印报表
    /// </summary>
    /// <returns></returns>
    public string Value(Customer customer)
    {
        string result = HeaderString(customer);
        var rentals = customer.GetRentals();
        foreach (var rental in rentals)
        {
            result += EachRentalsString(rental);
        }
        //add footer lines
        result += FooterString(customer);
        return result;
    }
}

所有这些都修改完毕之后,两个Value()函数看上去已经非常相似了,因此可以使用Pull Up Method将它们提升到基类中。提升完毕后,需要在基类中把子函数声明为抽象函数。

public abstract class Statement
{
    public abstract string HeaderString(Customer customer);
    public abstract string EachRentalsString(Rental rental);
    public abstract string FooterString(Customer customer);

    public string Value(Customer customer)
    {
        string result = HeaderString(customer);
        var rentals = customer.GetRentals();
        foreach (var rental in rentals)
        {
            result += EachRentalsString(rental);
        }
        //add footer lines
        result += FooterString(customer);
        return result;
    }
}

class TextStatement : Statement
{

    public override string HeaderString(Customer customer)
    {
        return "Rental Record for " + customer.Name + "\n";
    }

    public override string EachRentalsString(Rental rental)
    {
        return "\t" + rental.Title + "\t" + rental.GetCharge().ToString() + "\n";
    }

    public override string FooterString(Customer customer)
    {
        return "Amount owed is " + customer.GetTotalCharge() + "\n" +
         "You earned " + customer.GetTotalFrequentRenterPoints() + " frequent renter points";
    }

}

class HtmlStatement : Statement
{

    public override string HeaderString(Customer customer)
    {
        return "<h1>Rental Record for <em>" + customer.Name + "</em></h1>\n";
    }

    public override string EachRentalsString(Rental rental)
    {
        return rental.Title + ": " + rental.GetCharge().ToString() + "<br/>\n";
    }

    public override string FooterString(Customer customer)
    {
        return "<p>You owe  <em>" + customer.GetTotalCharge() + "<em/></p>\n" +
               "On this rental you earned <em>" + customer.GetTotalFrequentRenterPoints() + "</em> frequent renter points";
    }

}

完成本重构后,处理其他种类的报表就容易多了:只需为Statement再建一个子类,并在其中覆写3个抽象函数即可。

小结

模板方法模式是基于继承的代码复用技术,它体现了面向对象的诸多重要思想,是一种使用较为频繁的模式。模板方法模式被广泛应用于框架设计中,以确保通过父类来控制处理流程的逻辑顺序。

 

 

To Be Continued……

 

posted @ 2017-12-11 08:53  NaYoung  阅读(670)  评论(1编辑  收藏  举报