Form Template Method
《重构》中此方法叫做塑造模板函数,在设计模式中,对应的模式就是模板模式。重构中的很多变动比较大的方法都会导致重构,但重构中有非常多的小重构手法。就好像建筑一个房子,设计模式教你厨房客厅怎么搭配以设计出一个什么样的风格,而重构中给出了更多的建议,细小的细节,哪些地方应该怎么处理,会导致程序易读、易维护、易扩展。塑造模板函数是一个非常重要的重构手法,熟练运用此手法能直接将过程化的代码“变得”那么面向对象。
重构前的主体代码:
1 package io.nelson; 2 3 import java.util.Enumeration; 4 import java.util.Vector; 5 6 public class Customer { 7 8 private String _name; 9 private Vector<Rental> _rentals = new Vector<Rental>(); 10 11 public Customer(String name){ 12 _name = name; 13 } 14 15 public void addRental(Rental arg) 16 { 17 _rentals.addElement(arg); 18 } 19 20 public String getName(){ 21 return _name; 22 } 23 24 public String TextStatement(){ 25 double totalAmount = 0; 26 int frequentRenterPoints = 0; 27 Enumeration<Rental> rentals = _rentals.elements(); 28 String result = "Rental Record for "+getName()+"\n"; 29 30 while(rentals.hasMoreElements()) 31 { 32 double thisAmount = 0; 33 Rental each = (Rental) rentals.nextElement(); 34 35 switch(each.getMoive().getPriceCode()) 36 { 37 case Movie.REGULAR: 38 thisAmount += 2; 39 if(each.getDaysRented() > 2) 40 { 41 thisAmount += (each.getDaysRented()-2)*1.5; 42 } 43 break; 44 case Movie.NEW_RELEASE: 45 thisAmount += each.getDaysRented()*3; 46 break; 47 case Movie.CHILDRENS: 48 thisAmount += 1.5; 49 if(each.getDaysRented() > 3) 50 { 51 thisAmount += (each.getDaysRented()-3)*1.5; 52 } 53 break; 54 } 55 56 frequentRenterPoints++; 57 if((each.getMoive().getPriceCode() == Movie.NEW_RELEASE)&& 58 each.getDaysRented()>1) frequentRenterPoints++; 59 60 result += "\t" + each.getMoive().getTitle()+ "\t"+String.valueOf(thisAmount)+"\n"; 61 totalAmount += thisAmount; 62 } 63 64 result += "Amount owed is "+String.valueOf(totalAmount)+"\n"; 65 result += "You earned "+String.valueOf(frequentRenterPoints)+" frequent renter points"; 66 67 return result; 68 } 69 } 70 71 //租赁类 72 class Rental { 73 74 private Movie _movie; 75 private int _daysRented; 76 77 public Rental(Movie movie,int daysRented) 78 { 79 _movie = movie; 80 _daysRented = daysRented; 81 } 82 83 public int getDaysRented() 84 { 85 return _daysRented; 86 } 87 88 public Movie getMoive() 89 { 90 return _movie; 91 } 92 } 93 94 class Movie 95 { 96 public static final int CHILDRENS = 2; 97 public static final int REGULAR = 0; 98 public static final int NEW_RELEASE = 1; 99 100 private String _title; 101 private int _priceCode; 102 103 public Movie(String title,int priceCode) 104 { 105 _title = title; 106 _priceCode = priceCode; 107 } 108 109 public int getPriceCode(){ 110 return _priceCode; 111 } 112 113 public String getTitle() 114 { 115 return _title; 116 } 117 }
重构前的应用代码:
1 package io.nelson; 2 3 public class main { 4 5 public static void main(String[] args) { 6 7 System.out.println("Hello Moto!"); 8 9 //4部电影 10 Movie txwz = new Movie("天下无贼",Movie.REGULAR); 11 Movie gf = new Movie("功夫",Movie.NEW_RELEASE); 12 Movie gfxm = new Movie("功夫熊猫",Movie.CHILDRENS); 13 Movie bxqy = new Movie("冰雪奇缘",Movie.CHILDRENS); 14 15 //顾客小明 16 Customer xiaoming = new Customer("xiaoming"); 17 18 //4个租赁 19 Rental r1 = new Rental(txwz,4); //天下无贼租赁4天 20 Rental r2 = new Rental(gf,3); //功夫租赁3天 21 Rental r3 = new Rental(gfxm,7); //功夫熊猫租赁7天 22 Rental r4 = new Rental(bxqy,7); //冰雪奇缘租赁7天 23 24 xiaoming.addRental(r1); 25 xiaoming.addRental(r2); 26 xiaoming.addRental(r3); 27 xiaoming.addRental(r4); 28 29 System.out.println(xiaoming.TextStatement()); 30 } 31 }
Form Template Method这个重构方法其实非常简单,但要给出一个很好的例子能让人充分体会这个重构方法的优美得好好准备一下。原本想直接从《重构》中Form Template Method这一章节里把例子拿过来,但原书写到这了之后,跟第一章关联太大,要么从第一张重头来,那么这个例子就很难看懂了。想了想还是从第一章的例子从头开始吧,虽然费点时间。把重构前的代码抄了一遍,重构前的应用代码是我添加的。第一次读《重构》时读完第一章感觉就是“窝草,真牛逼”,现在这书也看了几遍,抄这个代码的时候就已经知道所有的重构技巧了,瞬间觉得这个例子简单了,要知道这个例子在书中可是用了50页的篇幅来写的。
先重构第一版,达到使用Form Template Method方法前的状态,今天要说的是Form Template Method,不能跑偏了。
本想一次拿出来最终重构代码,算了,这样出错的概率比较大,还是一步一步来。所以下面贴出重构过程,为了验证,或者说减少测试,先贴出上面的代码跑出来的正确结果,以此结果验证我后面每一步重构的正确性。
运行结果图
第一步重构后的主体代码:
1 package io.nelson; 2 3 import java.util.Enumeration; 4 import java.util.Vector; 5 6 public class Customer { 7 8 private String _name; 9 private Vector<Rental> _rentals = new Vector<Rental>(); 10 11 public Customer(String name){ 12 _name = name; 13 } 14 15 public void addRental(Rental arg) 16 { 17 _rentals.addElement(arg); 18 } 19 20 public String getName(){ 21 return _name; 22 } 23 24 public String TextStatement(){ 25 double totalAmount = 0; 26 int frequentRenterPoints = 0; 27 Enumeration<Rental> rentals = _rentals.elements(); 28 String result = "Rental Record for "+getName()+"\n"; 29 30 while(rentals.hasMoreElements()) 31 { 32 frequentRenterPoints++; 33 Rental each = (Rental) rentals.nextElement(); 34 35 if((each.getMoive().getPriceCode() == Movie.NEW_RELEASE)&& 36 each.getDaysRented()>1) frequentRenterPoints++; 37 38 result += "\t" + each.getMoive().getTitle()+ "\t"+String.valueOf(each.getAmount())+"\n"; 39 totalAmount += each.getAmount(); 40 } 41 42 result += "Amount owed is "+String.valueOf(totalAmount)+"\n"; 43 result += "You earned "+String.valueOf(frequentRenterPoints)+" frequent renter points"; 44 45 return result; 46 } 47 } 48 49 //租赁类 50 class Rental { 51 52 private Movie _movie; 53 private int _daysRented; 54 55 public Rental(Movie movie,int daysRented) 56 { 57 _movie = movie; 58 _daysRented = daysRented; 59 } 60 61 public int getDaysRented() 62 { 63 return _daysRented; 64 } 65 66 public Movie getMoive() 67 { 68 return _movie; 69 } 70 71 public double getAmount() 72 { 73 double amount=0; 74 switch(getMoive().getPriceCode()) 75 { 76 case Movie.REGULAR: 77 amount += 2; 78 if(getDaysRented() > 2) 79 { 80 amount += (getDaysRented()-2)*1.5; 81 } 82 break; 83 case Movie.NEW_RELEASE: 84 amount += getDaysRented()*3; 85 break; 86 case Movie.CHILDRENS: 87 amount += 1.5; 88 if(getDaysRented() > 3) 89 { 90 amount += (getDaysRented()-3)*1.5; 91 } 92 break; 93 } 94 95 return amount; 96 } 97 } 98 99 class Movie 100 { 101 public static final int CHILDRENS = 2; 102 public static final int REGULAR = 0; 103 public static final int NEW_RELEASE = 1; 104 105 private String _title; 106 private int _priceCode; 107 108 public Movie(String title,int priceCode) 109 { 110 _title = title; 111 _priceCode = priceCode; 112 } 113 114 public int getPriceCode(){ 115 return _priceCode; 116 } 117 118 public String getTitle() 119 { 120 return _title; 121 } 122 }
上面将计算每个租赁的金额抽取出来放到了Rental类中,增加函数getAmount。这一步运行一下,结果也是对的。接着进行下一步重构。
第二步重构后的主体代码:
1 package io.nelson; 2 3 import java.util.Enumeration; 4 import java.util.Vector; 5 6 public class Customer { 7 8 private String _name; 9 private Vector<Rental> _rentals = new Vector<Rental>(); 10 11 public Customer(String name){ 12 _name = name; 13 } 14 15 public void addRental(Rental arg) 16 { 17 _rentals.addElement(arg); 18 } 19 20 public String getName(){ 21 return _name; 22 } 23 24 public String TextStatement(){ 25 double totalAmount = 0; 26 int frequentRenterPoints = 0; 27 Enumeration<Rental> rentals = _rentals.elements(); 28 String result = "Rental Record for "+getName()+"\n"; 29 30 while(rentals.hasMoreElements()) 31 { 32 frequentRenterPoints++; 33 Rental each = (Rental) rentals.nextElement(); 34 35 if((each.getMoive().getPriceCode() == Movie.NEW_RELEASE)&& 36 each.getDaysRented()>1) frequentRenterPoints++; 37 38 result += "\t" + each.getMoive().getTitle()+ "\t"+String.valueOf(each.getAmount())+"\n"; 39 totalAmount += each.getAmount(); 40 } 41 42 result += "Amount owed is "+String.valueOf(totalAmount)+"\n"; 43 result += "You earned "+String.valueOf(frequentRenterPoints)+" frequent renter points"; 44 45 return result; 46 } 47 } 48 49 //租赁类 50 class Rental { 51 52 private Movie _movie; 53 private int _daysRented; 54 55 public Rental(Movie movie,int daysRented) 56 { 57 _movie = movie; 58 _daysRented = daysRented; 59 } 60 61 public int getDaysRented() 62 { 63 return _daysRented; 64 } 65 66 public Movie getMoive() 67 { 68 return _movie; 69 } 70 71 public double getAmount() 72 { 73 return _movie.getAmount(getDaysRented()); 74 } 75 } 76 77 class Movie 78 { 79 public static final int CHILDRENS = 2; 80 public static final int REGULAR = 0; 81 public static final int NEW_RELEASE = 1; 82 83 private String _title; 84 private int _priceCode; 85 86 public Movie(String title,int priceCode) 87 { 88 _title = title; 89 _priceCode = priceCode; 90 } 91 92 public int getPriceCode(){ 93 return _priceCode; 94 } 95 96 public String getTitle() 97 { 98 return _title; 99 } 100 101 public double getAmount(int days) 102 { 103 double amount=0; 104 switch(getPriceCode()) 105 { 106 case Movie.REGULAR: 107 amount += 2; 108 if(days > 2) 109 { 110 amount += (days-2)*1.5; 111 } 112 break; 113 case Movie.NEW_RELEASE: 114 amount += days*3; 115 break; 116 case Movie.CHILDRENS: 117 amount += 1.5; 118 if(days > 3) 119 { 120 amount += (days-3)*1.5; 121 } 122 break; 123 default: 124 break; 125 } 126 127 return amount; 128 } 129 }
这里将获取影片租赁金额的计算直接放在了Movie类中,计算过程中需要一个参数--租赁时间,最简单的方法是传Rental类对象作为参数,我这里没有这么做,因为这样增加了类之间的耦合。现在的Movie类没有使用其他类,是一个高度独立的类,可以方便移动到任何其他地方的。这一步运行一下,结果也是对的。接着进行下一步重构。
1 package io.nelson; 2 3 import java.util.Enumeration; 4 import java.util.Vector; 5 6 public class Customer { 7 8 private String _name; 9 private Vector<Rental> _rentals = new Vector<Rental>(); 10 11 public Customer(String name){ 12 _name = name; 13 } 14 15 public void addRental(Rental arg) 16 { 17 _rentals.addElement(arg); 18 } 19 20 public String getName(){ 21 return _name; 22 } 23 24 public String TextStatement(){ 25 double totalAmount = 0; 26 int frequentRenterPoints = 0; 27 Enumeration<Rental> rentals = _rentals.elements(); 28 String result = "Rental Record for "+getName()+"\n"; 29 30 while(rentals.hasMoreElements()) 31 { 32 frequentRenterPoints++; 33 Rental each = (Rental) rentals.nextElement(); 34 35 frequentRenterPoints += each.getFrequentRetenterPoints(); 36 37 result += "\t" + each.getMoive().getTitle()+ "\t"+String.valueOf(each.getAmount())+"\n"; 38 totalAmount += each.getAmount(); 39 } 40 41 result += "Amount owed is "+String.valueOf(totalAmount)+"\n"; 42 result += "You earned "+String.valueOf(frequentRenterPoints)+" frequent renter points"; 43 44 return result; 45 } 46 } 47 48 //租赁类 49 class Rental { 50 51 private Movie _movie; 52 private int _daysRented; 53 54 public Rental(Movie movie,int daysRented) 55 { 56 _movie = movie; 57 _daysRented = daysRented; 58 } 59 60 public int getDaysRented() 61 { 62 return _daysRented; 63 } 64 65 public Movie getMoive() 66 { 67 return _movie; 68 } 69 70 public double getAmount() 71 { 72 return _movie.getAmount(getDaysRented()); 73 } 74 75 public int getFrequentRetenterPoints() 76 { 77 if((getMoive().getPriceCode() == Movie.NEW_RELEASE)&& 78 getDaysRented()>1) return 1; 79 else 80 return 0; 81 } 82 } 83 84 class Movie 85 { 86 public static final int CHILDRENS = 2; 87 public static final int REGULAR = 0; 88 public static final int NEW_RELEASE = 1; 89 90 private String _title; 91 private int _priceCode; 92 93 public Movie(String title,int priceCode) 94 { 95 _title = title; 96 _priceCode = priceCode; 97 } 98 99 public int getPriceCode(){ 100 return _priceCode; 101 } 102 103 public String getTitle() 104 { 105 return _title; 106 } 107 108 public double getAmount(int days) 109 { 110 double amount=0; 111 switch(getPriceCode()) 112 { 113 case Movie.REGULAR: 114 amount += 2; 115 if(days > 2) 116 { 117 amount += (days-2)*1.5; 118 } 119 break; 120 case Movie.NEW_RELEASE: 121 amount += days*3; 122 break; 123 case Movie.CHILDRENS: 124 amount += 1.5; 125 if(days > 3) 126 { 127 amount += (days-3)*1.5; 128 } 129 break; 130 default: 131 break; 132 } 133 134 return amount; 135 } 136 }
处理了下获取积分的代码,积分计算也不应该在Customer类中完成,把它放在了Retental类中。简单修改下后,运行结果也是对的。接着下一步重构。
1 package io.nelson; 2 3 import java.util.Enumeration; 4 import java.util.Vector; 5 6 public class Customer { 7 8 private String _name; 9 private Vector<Rental> _rentals = new Vector<Rental>(); 10 11 public Customer(String name){ 12 _name = name; 13 } 14 15 public void addRental(Rental arg) 16 { 17 _rentals.addElement(arg); 18 } 19 20 public String getName(){ 21 return _name; 22 } 23 24 public String TextStatement(){ 25 double totalAmount = 0; 26 int frequentRenterPoints = 0; 27 Enumeration<Rental> rentals = _rentals.elements(); 28 String result = "Rental Record for "+getName()+"\n"; 29 30 while(rentals.hasMoreElements()) 31 { 32 frequentRenterPoints++; 33 Rental each = (Rental) rentals.nextElement(); 34 35 frequentRenterPoints += each.getFrequentRetenterPoints(); 36 37 result += "\t" + each.getMoive().getTitle()+ "\t"+String.valueOf(each.getAmount())+"\n"; 38 totalAmount += each.getAmount(); 39 } 40 41 result += "Amount owed is "+String.valueOf(totalAmount)+"\n"; 42 result += "You earned "+String.valueOf(frequentRenterPoints)+" frequent renter points"; 43 44 return result; 45 } 46 } 47 48 //租赁类 49 class Rental { 50 51 private Movie _movie; 52 private int _daysRented; 53 54 public Rental(Movie movie,int daysRented) 55 { 56 _movie = movie; 57 _daysRented = daysRented; 58 } 59 60 public int getDaysRented() 61 { 62 return _daysRented; 63 } 64 65 public Movie getMoive() 66 { 67 return _movie; 68 } 69 70 public double getAmount() 71 { 72 return _movie.getAmount(getDaysRented()); 73 } 74 75 public int getFrequentRetenterPoints() 76 { 77 return _movie.getFrequentRetenterPoints(getDaysRented()); 78 } 79 } 80 81 class Movie 82 { 83 public static final int CHILDRENS = 2; 84 public static final int REGULAR = 0; 85 public static final int NEW_RELEASE = 1; 86 87 private String _title; 88 private int _priceCode; 89 90 public Movie(String title,int priceCode) 91 { 92 _title = title; 93 _priceCode = priceCode; 94 } 95 96 public int getPriceCode(){ 97 return _priceCode; 98 } 99 100 public String getTitle() 101 { 102 return _title; 103 } 104 105 public double getAmount(int days) 106 { 107 double amount=0; 108 switch(getPriceCode()) 109 { 110 case Movie.REGULAR: 111 amount += 2; 112 if(days > 2) 113 { 114 amount += (days-2)*1.5; 115 } 116 break; 117 case Movie.NEW_RELEASE: 118 amount += days*3; 119 break; 120 case Movie.CHILDRENS: 121 amount += 1.5; 122 if(days > 3) 123 { 124 amount += (days-3)*1.5; 125 } 126 break; 127 default: 128 break; 129 } 130 131 return amount; 132 } 133 134 public int getFrequentRetenterPoints(int days) 135 { 136 if((getPriceCode() == Movie.NEW_RELEASE)&& 137 days>1) return 1; 138 else 139 return 0; 140 } 141 }
继续将获取积分的函数下移至Movie类,这一点跟上面修改获取影片价格一样,这一步也是做了简单修改,运行一下,结果也是对的,接着进行下一步重构。
1 package io.nelson; 2 3 import java.util.Enumeration; 4 import java.util.Vector; 5 6 public class Customer { 7 8 private String _name; 9 private Vector<Rental> _rentals = new Vector<Rental>(); 10 11 public Customer(String name){ 12 _name = name; 13 } 14 15 public void addRental(Rental arg) 16 { 17 _rentals.addElement(arg); 18 } 19 20 public String getName(){ 21 return _name; 22 } 23 24 public String TextStatement(){ 25 Enumeration<Rental> rentals = _rentals.elements(); 26 String result = "Rental Record for "+getName()+"\n"; 27 28 while(rentals.hasMoreElements()) 29 { 30 Rental each = (Rental) rentals.nextElement(); 31 result += "\t" + each.getMoive().getTitle()+ "\t"+String.valueOf(each.getAmount())+"\n"; 32 } 33 34 result += "Amount owed is "+String.valueOf(getTotalAmount())+"\n"; 35 result += "You earned "+String.valueOf(getTotalfrequentRenterPoints())+" frequent renter points"; 36 37 return result; 38 } 39 40 private double getTotalAmount() 41 { 42 double totalAmount=0; 43 Enumeration<Rental> rentals = _rentals.elements(); 44 45 while(rentals.hasMoreElements()) 46 { 47 Rental each = (Rental) rentals.nextElement(); 48 49 totalAmount += each.getAmount(); 50 } 51 52 return totalAmount; 53 } 54 55 private int getTotalfrequentRenterPoints() 56 { 57 int frequentRenterPoints=0; 58 59 Enumeration<Rental> rentals = _rentals.elements(); 60 String result = "Rental Record for "+getName()+"\n"; 61 62 while(rentals.hasMoreElements()) 63 { 64 frequentRenterPoints++; 65 Rental each = (Rental) rentals.nextElement(); 66 67 frequentRenterPoints += each.getFrequentRetenterPoints(); 68 69 result += "\t" + each.getMoive().getTitle()+ "\t"+String.valueOf(each.getAmount())+"\n"; 70 } 71 72 return frequentRenterPoints; 73 } 74 } 75 76 //租赁类 77 class Rental { 78 79 private Movie _movie; 80 private int _daysRented; 81 82 public Rental(Movie movie,int daysRented) 83 { 84 _movie = movie; 85 _daysRented = daysRented; 86 } 87 88 public int getDaysRented() 89 { 90 return _daysRented; 91 } 92 93 public Movie getMoive() 94 { 95 return _movie; 96 } 97 98 public double getAmount() 99 { 100 return _movie.getAmount(getDaysRented()); 101 } 102 103 public int getFrequentRetenterPoints() 104 { 105 return _movie.getFrequentRetenterPoints(getDaysRented()); 106 } 107 } 108 109 class Movie 110 { 111 public static final int CHILDRENS = 2; 112 public static final int REGULAR = 0; 113 public static final int NEW_RELEASE = 1; 114 115 private String _title; 116 private int _priceCode; 117 118 public Movie(String title,int priceCode) 119 { 120 _title = title; 121 _priceCode = priceCode; 122 } 123 124 public int getPriceCode(){ 125 return _priceCode; 126 } 127 128 public String getTitle() 129 { 130 return _title; 131 } 132 133 public double getAmount(int days) 134 { 135 double amount=0; 136 switch(getPriceCode()) 137 { 138 case Movie.REGULAR: 139 amount += 2; 140 if(days > 2) 141 { 142 amount += (days-2)*1.5; 143 } 144 break; 145 case Movie.NEW_RELEASE: 146 amount += days*3; 147 break; 148 case Movie.CHILDRENS: 149 amount += 1.5; 150 if(days > 3) 151 { 152 amount += (days-3)*1.5; 153 } 154 break; 155 default: 156 break; 157 } 158 159 return amount; 160 } 161 162 public int getFrequentRetenterPoints(int days) 163 { 164 if((getPriceCode() == Movie.NEW_RELEASE)&& 165 days>1) return 1; 166 else 167 return 0; 168 } 169 }
这一步修改的比较多一点,主要还是在TextStatement函数中,那个循环了其实干了三件事,第一计算价格,第二计算积分,第三“打印”各个租赁影片金额。上面这步重构把三个功能分开,这样就清晰多了。运行一下,结果也是对的。
下面添加HtmlStatement函数,要讲的From Template Method算正式开始了。
重构前的主体代码:
1 package io.nelson; 2 3 import java.util.Enumeration; 4 import java.util.Vector; 5 6 public class Customer { 7 8 private String _name; 9 private Vector<Rental> _rentals = new Vector<Rental>(); 10 11 public Customer(String name){ 12 _name = name; 13 } 14 15 public void addRental(Rental arg) 16 { 17 _rentals.addElement(arg); 18 } 19 20 public String getName(){ 21 return _name; 22 } 23 24 public String TextStatement(){ 25 Enumeration<Rental> rentals = _rentals.elements(); 26 String result = "Rental Record for "+getName()+"\n"; 27 28 while(rentals.hasMoreElements()) 29 { 30 Rental each = (Rental) rentals.nextElement(); 31 result += "\t" + each.getMoive().getTitle()+ "\t"+String.valueOf(each.getAmount())+"\n"; 32 } 33 34 result += "Amount owed is "+String.valueOf(getTotalAmount())+"\n"; 35 result += "You earned "+String.valueOf(getTotalfrequentRenterPoints())+" frequent renter points"; 36 37 return result; 38 } 39 40 public String HtmlStatement(){ 41 Enumeration<Rental> rentals = _rentals.elements(); 42 String result = "<H1>Rental Record for <EM>"+getName()+"</EM><H1><P>\n"; 43 44 while(rentals.hasMoreElements()) 45 { 46 Rental each = (Rental) rentals.nextElement(); 47 result += each.getMoive().getTitle()+ ":"+String.valueOf(each.getAmount())+"<BR>\n"; 48 } 49 50 result += "<P>You owe <EM>"+String.valueOf(getTotalAmount())+"</EM><P>\n"; 51 result += "On this rental you earned <EM>"+String.valueOf(getTotalfrequentRenterPoints())+"</EM> frequent renter points<P>"; 52 53 return result; 54 } 55 56 private double getTotalAmount() 57 { 58 double totalAmount=0; 59 Enumeration<Rental> rentals = _rentals.elements(); 60 61 while(rentals.hasMoreElements()) 62 { 63 Rental each = (Rental) rentals.nextElement(); 64 65 totalAmount += each.getAmount(); 66 } 67 68 return totalAmount; 69 } 70 71 private int getTotalfrequentRenterPoints() 72 { 73 int frequentRenterPoints=0; 74 75 Enumeration<Rental> rentals = _rentals.elements(); 76 String result = "Rental Record for "+getName()+"\n"; 77 78 while(rentals.hasMoreElements()) 79 { 80 frequentRenterPoints++; 81 Rental each = (Rental) rentals.nextElement(); 82 83 frequentRenterPoints += each.getFrequentRetenterPoints(); 84 85 result += "\t" + each.getMoive().getTitle()+ "\t"+String.valueOf(each.getAmount())+"\n"; 86 } 87 88 return frequentRenterPoints; 89 } 90 } 91 92 //租赁类 93 class Rental { 94 95 private Movie _movie; 96 private int _daysRented; 97 98 public Rental(Movie movie,int daysRented) 99 { 100 _movie = movie; 101 _daysRented = daysRented; 102 } 103 104 public int getDaysRented() 105 { 106 return _daysRented; 107 } 108 109 public Movie getMoive() 110 { 111 return _movie; 112 } 113 114 public double getAmount() 115 { 116 return _movie.getAmount(getDaysRented()); 117 } 118 119 public int getFrequentRetenterPoints() 120 { 121 return _movie.getFrequentRetenterPoints(getDaysRented()); 122 } 123 } 124 125 class Movie 126 { 127 public static final int CHILDRENS = 2; 128 public static final int REGULAR = 0; 129 public static final int NEW_RELEASE = 1; 130 131 private String _title; 132 private int _priceCode; 133 134 public Movie(String title,int priceCode) 135 { 136 _title = title; 137 _priceCode = priceCode; 138 } 139 140 public int getPriceCode(){ 141 return _priceCode; 142 } 143 144 public String getTitle() 145 { 146 return _title; 147 } 148 149 public double getAmount(int days) 150 { 151 double amount=0; 152 switch(getPriceCode()) 153 { 154 case Movie.REGULAR: 155 amount += 2; 156 if(days > 2) 157 { 158 amount += (days-2)*1.5; 159 } 160 break; 161 case Movie.NEW_RELEASE: 162 amount += days*3; 163 break; 164 case Movie.CHILDRENS: 165 amount += 1.5; 166 if(days > 3) 167 { 168 amount += (days-3)*1.5; 169 } 170 break; 171 default: 172 break; 173 } 174 175 return amount; 176 } 177 178 public int getFrequentRetenterPoints(int days) 179 { 180 if((getPriceCode() == Movie.NEW_RELEASE)&& 181 days>1) return 1; 182 else 183 return 0; 184 } 185 }
重构前的应用代码(比上面多加了两行语句):
1 package io.nelson; 2 3 public class main { 4 5 public static void main(String[] args) { 6 7 System.out.println("Hello Moto!"); 8 9 //4部电影 10 Movie txwz = new Movie("天下无贼",Movie.REGULAR); 11 Movie gf = new Movie("功夫",Movie.NEW_RELEASE); 12 Movie gfxm = new Movie("功夫熊猫",Movie.CHILDRENS); 13 Movie bxqy = new Movie("冰雪奇缘",Movie.CHILDRENS); 14 15 //顾客小明 16 Customer xiaoming = new Customer("xiaoming"); 17 18 //4个租赁 19 Rental r1 = new Rental(txwz,4); //天下无贼租赁4天 20 Rental r2 = new Rental(gf,3); //功夫租赁3天 21 Rental r3 = new Rental(gfxm,7); //功夫熊猫租赁7天 22 Rental r4 = new Rental(bxqy,7); //冰雪奇缘租赁7天 23 24 xiaoming.addRental(r1); 25 xiaoming.addRental(r2); 26 xiaoming.addRental(r3); 27 xiaoming.addRental(r4); 28 29 System.out.println(xiaoming.TextStatement()); 30 System.out.println(); 31 System.out.println(xiaoming.HtmlStatement()); 32 } 33 }
重构后的主体代码:
1 package io.nelson; 2 3 import java.util.Enumeration; 4 import java.util.Vector; 5 6 public class Customer { 7 8 private String _name; 9 private Vector<Rental> _rentals = new Vector<Rental>(); 10 HtmlStatement htmlStatement; 11 TextStatement textStatement; 12 13 public Customer(String name){ 14 _name = name; 15 16 htmlStatement = new HtmlStatement(); 17 textStatement = new TextStatement(); 18 } 19 20 public void addRental(Rental arg) 21 { 22 _rentals.addElement(arg); 23 } 24 25 public Vector<Rental> getRetentals() 26 { 27 return _rentals; 28 } 29 30 public String getName(){ 31 return _name; 32 } 33 34 public String HtmlStatement() 35 { 36 return htmlStatement.statement(this); 37 } 38 39 public String TextStatement() 40 { 41 return textStatement.statement(this); 42 } 43 44 public double getTotalAmount() 45 { 46 double totalAmount=0; 47 Enumeration<Rental> rentals = _rentals.elements(); 48 49 while(rentals.hasMoreElements()) 50 { 51 Rental each = (Rental) rentals.nextElement(); 52 53 totalAmount += each.getAmount(); 54 } 55 56 return totalAmount; 57 } 58 59 public int getTotalfrequentRenterPoints() 60 { 61 int frequentRenterPoints=0; 62 63 Enumeration<Rental> rentals = _rentals.elements(); 64 65 while(rentals.hasMoreElements()) 66 { 67 frequentRenterPoints++; 68 Rental each = (Rental) rentals.nextElement(); 69 70 frequentRenterPoints += each.getFrequentRetenterPoints(); 71 } 72 73 return frequentRenterPoints; 74 } 75 } 76 77 abstract class Statement 78 { 79 public String statement(Customer aCus){ 80 Enumeration<Rental> rentals = aCus.getRetentals().elements(); 81 String result = headerString(aCus); 82 83 while(rentals.hasMoreElements()) 84 { 85 Rental each = (Rental) rentals.nextElement(); 86 result += eachString(each); 87 } 88 89 result += footerString(aCus); 90 91 return result; 92 } 93 94 abstract String headerString(Customer aCus); 95 abstract String footerString(Customer aCus); 96 abstract String eachString(Rental arent); 97 } 98 99 class HtmlStatement extends Statement 100 { 101 public String footerString(Customer aCus) 102 { 103 String result = ""; 104 result += "<P>You owe <EM>"+String.valueOf(aCus.getTotalAmount())+"</EM><P>\n"; 105 result += "On this rental you earned <EM>"+String.valueOf(aCus.getTotalfrequentRenterPoints())+"</EM> frequent renter points<P>"; 106 107 return result; 108 } 109 110 public String headerString(Customer aCus) 111 { 112 return ("<H1>Rental Record for <EM>"+aCus.getName()+"</EM><H1><P>\n"); 113 } 114 115 public String eachString(Rental arent) 116 { 117 return (arent.getMoive().getTitle()+ ":"+String.valueOf(arent.getAmount())+"<BR>\n"); 118 } 119 } 120 121 class TextStatement extends Statement 122 { 123 public String footerString(Customer aCus) 124 { 125 String result=""; 126 result += "Amount owed is "+String.valueOf(aCus.getTotalAmount())+"\n"; 127 result += "You earned "+String.valueOf(aCus.getTotalfrequentRenterPoints())+" frequent renter points"; 128 return result; 129 } 130 131 public String headerString(Customer aCus) 132 { 133 return ("Rental Record for "+aCus.getName()+"\n"); 134 } 135 136 public String eachString(Rental arent) 137 { 138 return ("\t"+arent.getMoive().getTitle()+ "\t"+String.valueOf(arent.getAmount())+"\n"); 139 } 140 } 141 142 //租赁类 143 class Rental { 144 145 private Movie _movie; 146 private int _daysRented; 147 148 public Rental(Movie movie,int daysRented) 149 { 150 _movie = movie; 151 _daysRented = daysRented; 152 } 153 154 public int getDaysRented() 155 { 156 return _daysRented; 157 } 158 159 public Movie getMoive() 160 { 161 return _movie; 162 } 163 164 public double getAmount() 165 { 166 return _movie.getAmount(getDaysRented()); 167 } 168 169 public int getFrequentRetenterPoints() 170 { 171 return _movie.getFrequentRetenterPoints(getDaysRented()); 172 } 173 } 174 175 class Movie 176 { 177 public static final int CHILDRENS = 2; 178 public static final int REGULAR = 0; 179 public static final int NEW_RELEASE = 1; 180 181 private String _title; 182 private int _priceCode; 183 184 public Movie(String title,int priceCode) 185 { 186 _title = title; 187 _priceCode = priceCode; 188 } 189 190 public int getPriceCode(){ 191 return _priceCode; 192 } 193 194 public String getTitle() 195 { 196 return _title; 197 } 198 199 public double getAmount(int days) 200 { 201 double amount=0; 202 switch(getPriceCode()) 203 { 204 case Movie.REGULAR: 205 amount += 2; 206 if(days > 2) 207 { 208 amount += (days-2)*1.5; 209 } 210 break; 211 case Movie.NEW_RELEASE: 212 amount += days*3; 213 break; 214 case Movie.CHILDRENS: 215 amount += 1.5; 216 if(days > 3) 217 { 218 amount += (days-3)*1.5; 219 } 220 break; 221 default: 222 break; 223 } 224 225 return amount; 226 } 227 228 public int getFrequentRetenterPoints(int days) 229 { 230 if((getPriceCode() == Movie.NEW_RELEASE)&& 231 days>1) return 1; 232 else 233 return 0; 234 } 235 }
重构后的应用代码:
1 package io.nelson; 2 3 public class main { 4 5 public static void main(String[] args) { 6 7 System.out.println("Hello Moto!"); 8 9 //4部电影 10 Movie txwz = new Movie("天下无贼",Movie.REGULAR); 11 Movie gf = new Movie("功夫",Movie.NEW_RELEASE); 12 Movie gfxm = new Movie("功夫熊猫",Movie.CHILDRENS); 13 Movie bxqy = new Movie("冰雪奇缘",Movie.CHILDRENS); 14 15 //顾客小明 16 Customer xiaoming = new Customer("xiaoming"); 17 18 //4个租赁 19 Rental r1 = new Rental(txwz,4); //天下无贼租赁4天 20 Rental r2 = new Rental(gf,3); //功夫租赁3天 21 Rental r3 = new Rental(gfxm,7); //功夫熊猫租赁7天 22 Rental r4 = new Rental(bxqy,7); //冰雪奇缘租赁7天 23 24 xiaoming.addRental(r1); 25 xiaoming.addRental(r2); 26 xiaoming.addRental(r3); 27 xiaoming.addRental(r4); 28 29 System.out.println(xiaoming.TextStatement()); 30 System.out.println(); 31 System.out.println(xiaoming.HtmlStatement()); 32 } 33 }
(代码贴到这里的是否发现上面计算积分函数里多了一个字符串语句,是拷贝的时候没有删除,上面已经贴了的就不改了,看最后这里就可以了。)
这一部分的代码也算完了,Form Template Method主要针对的是Statement类中的statement函数,在这个函数中,有一个过程化的函数,函数中有几个步骤。Form Template Method就是说,将函数中变化的部分抽出来,供不同子类去重写,函数中固定的部分保持不便。
“常见的一种情况是:两个函数以相同顺序执行大致相近的操作,但是各操作不完全相同。这种情况下我们可以将执行操作的序列移至超类,并借助多态保证各操作得以保持差异性,这样的函数被称为Template Method(模板函数)”。
我的师傅给我讲过一句话,“在面向对象的语言里,一切对象化。”从这个重构手法里,可以看到把函数类化,处理过程化代码重用、差异化更有效。