处理概括关系之十 :Form Template Method(塑造模板函数)

你有一些subclasses ,其中相应的某些函数以相同顺序执行类似的措施,但各措施实际上有所不同。

将各个措施分别放进独立函数中,并保持它们都有相同的签名式(signature),于是原函数也就变得相同了。然后将原函数上移至superclass 。

动机(Motivation)

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

常见的一种情况是:两个函数以相同序列(sequence)执行大致相近的措施,但是各措施不完全相同。这种情况下我们可以将「执行各措施」的序列移至superclass , 并倚赖多态(polymorphism )保证各措施仍得以保持差异性。这样的函数被称为Template Method (模板函数)[Gang of Four]。

作法(Mechanics)

·在各个subclass 中分解目标函数,使分解后的各个函数要不完全相同,要不完全不同。

·运用Pull Up Method 将各subclass 内寒全相同的函数上移至superclass 。

·对于那些(剩余的、存在于各subclasses 内的)完全不同的函数,实施Rename Method,使所有这些函数的签名式(signature)完全相同。
Ø这将使得原函数变为完全相同,因为它们都执行同样一组函数调用; 但各subclass 会以不同方式响应这些调用。

·修改上述所有签名式后,编译并测试。

·运用Pull Up Method 将所有原函数上移至superclass 。在superclass 中将那些「有所不同、代表各种不同措施」的函数定义为抽象函数。

·编译,测试。

·移除其他subclass 中的原函数,每删除一个,编译并测试。

范例:(Example)

现在我将完成第一章遗留的那个范例。在此范例中,我有一个Customer ,其中有两个用于打印的函数。statement() 函数以ASCII 码打印报表(statement):

   public String statement() {

      Enumeration rentals = _rentals.elements();

       String result = "Rental Record for " + getName() + "\n";

       while (rentals.hasMoreElements()) {

           Rental each = (Rental) rentals.nextElement();

           //show figures for this rental

           result += "\t" + each.getMovie().getTitle()+ "\t" +

               String.valueOf(each.getCharge()) + "\n";

       }

       //add footer lines

       result +=  "Amount owed is " + String.valueOf(getTotalCharge()) + "\n";

       result += "You earned " + String.valueOf(getTotalFrequentRenterPoints()) +

            " frequent renter points";

       return result;

   }

函数htmlStatement() 则以HTML 格式输出报表:

   public String htmlStatement() {

       Enumeration rentals = _rentals.elements();

       String result = "<H1>Rentals for <EM>" + getName() + "</EM></H1><P>\n";

       while (rentals.hasMoreElements()) {

           Rental each = (Rental) rentals.nextElement();

           //show figures for each rental

           result += each.getMovie().getTitle()+ ": " +

               String.valueOf(each.getCharge()) + "<BR>\n";

       }

       //add footer lines

       result +=  "<P>You owe <EM>" + String.valueOf(getTotalCharge()) + "</EM><P>\n";

       result += "On this rental you earned <EM>" +

           String.valueOf(getTotalFrequentRenterPoints()) +

           "</EM> frequent renter points<P>";

       return result;

   }

使用 Form Template Method 之前,我需要对上述两个函数做一些整理,使它们成为「某个共同superclass 」下的subclass 函数。为了这一目的,我使用函数对象(method object)[Beck] 针对「报表打印工作」创建一个「独立的策略继承体系」(separate strategy hierarchy ),如图11.1。

图11.1  针对「报表输出」使用Stategy 模式

class Statement {}

class TextStatement extends Statement {}

class HtmlStatement extends Statement {}

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

class Customer...

public String statement() {

   return new TextStatement().value(this);

}

public String htmlStatement() {

   return new HtmlStatement().value(this);

}

      

class TextStatement {

  public String value(Customer aCustomer) {

        Enumeration rentals = aCustomer.getRentals();

        String result = "Rental Record for " + aCustomer.getName() + "\n";

        while (rentals.hasMoreElements()) {

            Rental each = (Rental) rentals.nextElement();

            //show figures for this rental

            result += "\t" + each.getMovie().getTitle()+ "\t" +

                String.valueOf(each.getCharge()) + "\n";

        }

        //add footer lines

        result +=  "Amount owed is " + String.valueOf(aCustomer.getTotalCharge()) + "\n";

        result += "You earned " + String.valueOf(aCustomer.getTotalFrequentRenterPoints()) +

          " frequent renter points";

        return result;

   }

class HtmlStatement {

  public String value(Customer aCustomer) {

        Enumeration rentals = aCustomer.getRentals();

        String result = "<H1>Rentals for <EM>" + aCustomer.getName() + "</EM></H1><P>\n";

        while (rentals.hasMoreElements()) {

            Rental each = (Rental) rentals.nextElement();

            //show figures for each rental

            result += each.getMovie().getTitle()+ ": " +

                        String.valueOf(each.getCharge()) + "<BR>\n";

        }

        //add footer lines

        result +=  "<P>You owe <EM>" + String.valueOf(aCustomer.getTotalCharge()) +

             "</EM><P>\n";

        result += "On this rental you earned <EM>"

                String.valueOf(aCustomer.getTotalFrequentRenterPoints()) +

                "</EM> frequent renter points<P>";

        return result;

}

搬移之后,我还对这两个函数的名称做了一些修改,使它们更好地适应Strategy 模式的要求。我之所以为它们取相同名称,因为两者之间的差异不在于函数,而在于函数所属的class 。如果你想试着编译这段代码,还必须在Customer class 中添加一个getRentals() 函数,并放宽getTotalCharge() 函数和getTotalFrequentRenterPoints() 函数的可视性(visibility )。

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

第一个例子就是打印报表表头(headers)。上述两个函数都通过Customer 对象获取信息,但对运算结果(字符串)的格式化方式不同。我可以将「对字符串的格式化动作」提炼到独立函数中,并将提炼所得命以相同的签名式(signature):

class TextStatement...

  String headerString(Customer aCustomer) {

    return "Rental Record for " + aCustomer.getName() + "\n";

  }

  public String value(Customer aCustomer) {

        Enumeration rentals = aCustomer.getRentals();

        String result =headerString(aCustomer);

        while (rentals.hasMoreElements()) {

            Rental each = (Rental) rentals.nextElement();

            //show figures for this rental

            result += "\t" + each.getMovie().getTitle()+ "\t" +

                String.valueOf(each.getCharge()) + "\n";

        }

        //add footer lines

        result +=  "Amount owed is " + String.valueOf(aCustomer.getTotalCharge()) + "\n";

        result += "You earned " + String.valueOf(aCustomer.getTotalFrequentRenterPoints()) +

             " frequent renter points";

        return result;

   }

class HtmlStatement...

  String headerString(Customer aCustomer) {

        return "<H1>Rentals for <EM>" + aCustomer.getName() + "</EM></H1><P>\n";

}

public String value(Customer aCustomer) {

      Enumeration rentals = aCustomer.getRentals();

      String result = headerString(aCustomer);

      while (rentals.hasMoreElements()) {

          Rental each = (Rental) rentals.nextElement();

          //show figures for each rental

          result += each.getMovie().getTitle()+ ": " +

                      String.valueOf(each.getCharge()) + "<BR>\n";

      }

      //add footer lines

    result +=  "<P>You owe <EM>" + String.valueOf(aCustomer.getTotalCharge()) + "</ EM><P>\n";

     result += "On this rental you earned <EM>" +

         String.valueOf(aCustomer.getTotalFrequentRenterPoints()) +

         "</EM> frequent renter points<P>";

     return result;

}

编译并测试,然后继续处理其他元素。我将逐一对各个元素进行上述过程。下面是整个重构完成后的结果:

class TextStatement …

  public String value(Customer aCustomer) {

      Enumeration rentals = aCustomer.getRentals();

      String result = headerString(aCustomer);

      while (rentals.hasMoreElements()) {

          Rental each = (Rental) rentals.nextElement();

          result += eachRentalString(each);

      }

      result += footerString(aCustomer);

      return result;

   }

String eachRentalString (Rental aRental) {

      return "\t" + aRental.getMovie().getTitle()+ "\t" +

          String.valueOf(aRental.getCharge()) + "\n";

    }

String footerString (Customer aCustomer) {

        return "Amount owed is " + String.valueOf(aCustomer.getTotalCharge()) + "\n" +

           "You earned " + String.valueOf(aCustomer.getTotalFrequentRenterPoints()) +

           " frequent renter points";

    }

class HtmlStatement…

  public String value(Customer aCustomer) {

        Enumeration rentals = aCustomer.getRentals();

        String result = headerString(aCustomer);

        while (rentals.hasMoreElements()) {

            Rental each = (Rental) rentals.nextElement();

            result += eachRentalString(each);

        }

        result += footerString(aCustomer);

        return result;

  }

  String eachRentalString (Rental aRental) {

      return aRental.getMovie().getTitle()+ ": " +

          String.valueOf(aRental.getCharge()) + "<BR>\n";

  }

  String footerString (Customer aCustomer) {

        return "<P>You owe <EM>" + String.valueOf(aCustomer.getTotalCharge()) +

        "</EM><P>

" + "On this rental you earned <EM>" +

        String.valueOf(aCustomer.getTotalFrequentRenterPoints()) +

          "</EM> frequent renter points<P>";

  }

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

class Statement...

  public String value(Customer aCustomer) {

        Enumeration rentals = aCustomer.getRentals();

        String result = headerString(aCustomer);

        while (rentals.hasMoreElements()) {

            Rental each = (Rental) rentals.nextElement();

            result += eachRentalString(each);

        }

        result +=  footerString(aCustomer);

        return result;

    }

  abstract String headerString(Customer aCustomer);

  abstract String eachRentalString (Rental aRental);

  abstract String footerString (Customer aCustomer);

然后我把TextStatement.value() 函数拿掉,编译并测试。完成之后再把HtmlStatement.value() 也删掉,再次编译并测试。最后结果如图11.2。

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

图11.2  Templae Method(模板函数)塑造完毕后的classes

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值