重构类关系-Form Template Method塑造模板函数十

重构类关系-Form Template Method塑造模板函数十

1.塑造模板函数

1.1.使用场景

你有一些子类,其中相应的某些函数以相同顺序执行类似的操作,但各个操作的细节上有所不同。
将这些操作分别放进独立函数中,并保持它们都有相同的签名,于是原函数也就变得相同了。然后将原函数上移至超类。

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

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

1.2.如何做

  • 在各个子类中分解目标函数,使分解后的各个函数要不完全相同,要不完全不同。
  • 运用Pull Up Method (322)将各子类内完全相同的函数上移至超类。
  • 对于那些(剩余的、存在于各子类内的)完全不同的函数,实施Rename Method (273),使所有这些函数的签名完全相同。
  • 这将使得原函数变为完全相同,因为它们都执行同样一组函数调用;但各子类会以不同方式响应这些调用。
  • 修改上述所有签名后,编译并测试。
  • 运用Pull Up Method (322)将所有原函数逐一上移至超类。在超类中将那些代表各种不同操作的函数定义为抽象函数。
  • 编译,测试。
  • 移除其他子类中的原函数,每删除一个,编译并测试。

1.3.示例

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

   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 (345)之前,我需要对上述两个函数做一些整理,使它们成为同一个超类下的子类函数。为了这一目的,我使用函数对象 [Beck]针对“报表打印”创建一个独立的策略继承体系,

class Statement {
    
    }
class TextStatement extends Statement {
    
    }
class HtmlStatement extends Statement {
    
    }

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

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模式的要求。我之所以为它们取相同名称,因为两者之间的差异不在于函数,而在于函数所属的类。如果你想试着编译这段代码,还必须在Customer类中添加一个getRentals()函数,并放宽getTotalCharge()函数和getTotalFrequent-RenterPoints()函数的可见度。

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

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

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 TextStatementpublic 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 HtmlStatementpublic 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 (322)将它们提升到超类中。提升完毕后,我需要在超类中把子类函数声明为抽象函数。

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()也删掉,再次编译并测试。
完成本重构后,处理其他种类的报表就容易多了:你只需为Statement再建一个子类,并在其中覆写3个抽象函数即可。

猜你喜欢

转载自blog.csdn.net/m0_38039437/article/details/129789632