上一篇文章:java 代码重构-第一章(运用多态(Polymorphism)取代与价格相关的条件逻辑)
下一篇文章:java 代码重构-第一章(使用策略模式,把恶心的switch代码去掉...) 一
终于……我们来到继承(Inheritance)
我们有数种影片类型,它们以不同的方式回答相同的问题。这听起来很像subclasses 的工作。我们可以建立Movie 的三个subclasses ,每个都有自己的计费法(图1.14)。
这么一来我就可以运用多态(polymorphism)来取代switch 语句了。很遗憾的是这里有个小问题,不能这么干。一部影片可以在生命周期内修改自己的分类,一个对象却不能在生命周期内修改自己所属的class。不过还是有一个解决方法:State pattern(模式)[Gang of Four]。运用它之后,我们的classes 看起来像图1.15。
加入这一层间接性,我们就可以在Price 对象内进行subclassing 动作(译注:一如图1.15),于是便可在任何必要时刻修改价格。
如果你很熟悉Gang of Four 所列的各种模式(patterns),你可能会问:『这是一个State 还是一个Strategy?』答案取决于Price class 究竟代表计费方式(此时我喜欢把它叫做Pricer 或PricingStrategy),或是代表影片的某个状态(state,例如「Star Trek X 是一部新片」)。在这个阶段,对于模式(和其名称)的选择反映出你对结构的想法。此刻我把它视为影片的某种状态(state)。如果未来我觉得Strategy 能更好地说明我的意图,我会再重构它,修改名字,以形成Strategy 。
为了引入State 模式,我使用三个重构准则。首先运用Replace Type Code with State/Strategy,将「与型别相依的行为」(type code behavior )搬移至State 模式内。然后运用Move Method 将switch 语句移到Price class 里头。最后运用Replace Conditional with Polymorphism去掉switch 语句。
首先我要使用Replace Type Code with State/Strategy。第一步骤是针对「与 型别相依的行为」使用Self Encapsulate Field,确保任何时候都通过getting 和setting 两个函数来运用这些行为。由于多数代码来自其他classes,所以多数函数都己经使用getting 函数。但构造函数(constructor )仍然直接访问价格代号(译注:程序中的priceCode):
public Movie(String title, int priceCode) { this.title = title; this.priceCode = priceCode; }
然后编译并测试,确保没有破坏任何东西。
输出结果:
Rental Record for oyhk 少林足球 6.0 大话西游 1.5 Amount owed is 7.5 You earned 3 frequent renter points ------------------------------------------------ <H1>Rentals for <EM>oyhk</EM></ H1><P> 少林足球: 6.0<BR> 大话西游: 1.5<BR> <P>You owe <EM>7.5</EM><P> On this rental you earned <EM>3</EM> frequent renter points<P>
证明重构没有错,结果跟上次的一样
下面是完整的代码:
Movie
package com.mkfree.refactoring.shap1; /** * 电影类 * * @author hk * * 2012-12-25 下午10:55:14 */ public class Movie { public static final int CHILDRENS = 2; public static final int REGULAR = 0; public static final int NEW_RELEASE = 1; public Movie(String title, int priceCode) { this.title = title; this.priceCode = priceCode; } private String title; private int priceCode; public String getTitle() { return title; } public int getPriceCode() { return priceCode; } /** * 获取收费 * * @param daysRented * @return */ double getCharge(int daysRented) { double result = 0; switch (getPriceCode()) { case Movie.REGULAR: result += 2; if (daysRented > 2) result += (daysRented - 2) * 1.5; break; case Movie.NEW_RELEASE: result += daysRented * 3; break; case Movie.CHILDRENS: result += 1.5; if (daysRented > 3) result += (daysRented - 3) * 1.5; break; } return result; } int getFrequentRenterPoints(int daysRented) { if ((getPriceCode() == Movie.NEW_RELEASE) && daysRented > 1) return 2; else return 1; } }
Rental
package com.mkfree.refactoring.shap1; /** * 租凭 * * @author hk * * 2012-12-25 下午10:57:00 */ public class Rental { private Movie movie; private int daysRented; public Rental(Movie movie, int daysRented) { this.movie = movie; this.daysRented = daysRented; } public Movie getMovie() { return movie; } public int getDaysRented() { return daysRented; } /** * 把代码迁移到movie中 * * @return */ double getCharge() { return this.getMovie().getCharge(daysRented); } /** * 获取经常的租赁 * * @return */ int getFrequentRenterPoints() { return this.getMovie().getFrequentRenterPoints(daysRented); } }
Customer
package com.mkfree.refactoring.shap1; import java.util.Enumeration; import java.util.Vector; /** * 顾客 * * @author hk * * 2012-12-25 下午10:59:03 */ public class Customer { private String name; private Vectorrentals = new Vector<>(); public Customer(String name) { this.name = name; } /** * 添加 * * @param rental */ public void addRentals(Rental rental) { rentals.add(rental); } public String getName() { return name; } /** * 通计清单 * * @return */ public String statement() { Enumerationenu_rentals = rentals.elements(); String result = "Rental Record for " + this.getName() + " \n"; while (enu_rentals.hasMoreElements()) { Rental each = enu_rentals.nextElement(); result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "\n"; } result += "Amount owed is " + String.valueOf(getTotalCharge()) + "\n"; result += "You earned " + String.valueOf(getTotalFrequentRenterPoints()) + " frequent renter points"; return result; } // 此即所谓query method private double getTotalCharge() { double result = 0; Enumerationenu_rentals = rentals.elements(); while (enu_rentals.hasMoreElements()) { Rental each = (Rental) enu_rentals.nextElement(); result += each.getCharge(); } return result; } // 此即所谓query method private int getTotalFrequentRenterPoints() { int result = 0; Enumerationenu_rentals = rentals.elements(); while (enu_rentals.hasMoreElements()) { Rental each = (Rental) enu_rentals.nextElement(); result += each.getFrequentRenterPoints(); } return result; } }
Client
package com.mkfree.refactoring.shap1; import org.junit.Test; public class Client { @Test public void testStatement() { Movie movie1 = new Movie("少林足球", 1); Rental rental1 = new Rental(movie1, 2); Movie movie2 = new Movie("大话西游", 2); Rental rental2 = new Rental(movie2, 3); Customer customer = new Customer("oyhk"); customer.addRentals(rental1); customer.addRentals(rental2); String statement = customer.statement(); System.out.println(statement); System.out.println("------------------------------------------------"); String htmlStatment = customer.htmlStatement(); System.out.println(htmlStatment); } }
由于重构了Movie代码,对于Client当然也修改了一点点了...