zoukankan      html  css  js  c++  java
  • 重构手法之处理概括关系【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 + "
    ";
    
            foreach (var rental in _rentals)
            {
                result += "	" + rental.Title + "	" + rental.GetCharge().ToString() + "
    ";
            }
            //add footer lines
            result += "Amount owed is " + GetTotalCharge() + "
    ";
            result += "You earned " + GetTotalFrequentRenterPoints() + " frequent renter points";
            return result;
        }
        public string HtmlStatement()
        {
            string result = "<h1>Rentals for <em>" + Name + "</em></h1>
    ";
    
            foreach (var rental in _rentals)
            {
                result += rental.Title + ": " + rental.GetCharge().ToString() + "<br/>
    ";
            }
            //add footer lines
            result += "<p>You owe  <em>" + GetTotalCharge() + "<em/></p>
    ";
            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 + "
    ";
            var rentals = customer.GetRentals();
            foreach (var rental in rentals)
            {
                result += "	" + rental.Title + "	" + rental.GetCharge().ToString() + "
    ";
            }
            //add footer lines
            result += "Amount owed is " + customer.GetTotalCharge() + "
    ";
            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>
    ";
            var rentals = customer.GetRentals();
            foreach (var rental in rentals)
            {
                result += rental.Title + ": " + rental.GetCharge().ToString() + "<br/>
    ";
            }
            //add footer lines
            result += "<p>You owe  <em>" + customer.GetTotalCharge() + "<em/></p>
    ";
            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 + "
    ";
        }
    
        public string EachRentalsString(Rental rental)
        {
            return "	" + rental.Title + "	" + rental.GetCharge().ToString() + "
    ";
        }
    
        public string FooterString(Customer customer)
        {
            return "Amount owed is " + customer.GetTotalCharge() + "
    " +
             "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>
    ";
        }
    
        public string EachRentalsString(Rental rental)
        {
            return rental.Title + ": " + rental.GetCharge().ToString() + "<br/>
    ";
        }
    
        public string FooterString(Customer customer)
        {
            return "<p>You owe  <em>" + customer.GetTotalCharge() + "<em/></p>
    " +
                   "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 + "
    ";
        }
    
        public override string EachRentalsString(Rental rental)
        {
            return "	" + rental.Title + "	" + rental.GetCharge().ToString() + "
    ";
        }
    
        public override string FooterString(Customer customer)
        {
            return "Amount owed is " + customer.GetTotalCharge() + "
    " +
             "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>
    ";
        }
    
        public override string EachRentalsString(Rental rental)
        {
            return rental.Title + ": " + rental.GetCharge().ToString() + "<br/>
    ";
        }
    
        public override string FooterString(Customer customer)
        {
            return "<p>You owe  <em>" + customer.GetTotalCharge() + "<em/></p>
    " +
                   "On this rental you earned <em>" + customer.GetTotalFrequentRenterPoints() + "</em> frequent renter points";
        }
    
    }

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

    小结

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

    To Be Continued……

  • 相关阅读:
    LeetCode 75. Sort Colors(按颜色进行排序)
    LeetCode 451. Sort Characters By Frequency(按照字符出现次数对字符串排序)
    LeetCode 347. Top K Frequent Elements(出现频率最多的 k 个元素)
    LeetCode 215. Kth Largest Element in an Array(数组求第k大)
    CF #629 Div.3 E(LCA)F
    系统函数
    CASE表达式
    循环得出数据库中所有的 DB_ID,DB_NAME
    数据库的编码问题
    检验临时表是否存在
  • 原文地址:https://www.cnblogs.com/liuyoung/p/7967493.html
Copyright © 2011-2022 走看看