返回总目录
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 linesresult += "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 linesresult += "<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 linesresult += "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 linesresult += "<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 linesresult += 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 linesresult += 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 linesresult += 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……