前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到教程。
上接 重构-改善既有代码的设计-第1例:租赁影片(1)
2 运用多态取代与价格相关的条件逻辑
2.1 最好不要在另一个对象的属性基础上运用switch语句,应该在对象自己的数据上使用。
2.1.1 移动 getCharge ,getFrequentRenterPoints 方法到Movie 类中去。把会根据影片类型的变化而变化的东西放在影片类中。
Movie 类改为:
package bean;/*** 影片* @author Administrator*/
public class Movie {public static final int CHILDRENS = 2; // 儿童片public static final int REGULAR = 0; // 普通片public static final int NEW_RELEASE = 1; // 新片private String _title;private int _priceCode;public Movie(String _title, int _priceCode) {this._title = _title;this._priceCode = _priceCode;}public int getPriceCode() {return _priceCode;}public void setPriceCode(int _priceCode) {this._priceCode = _priceCode;}public String getTitle() {return _title;}/*** 计算租金* @param dayRented 租赁天数* @return*/double getCharge(int dayRented){double result = 0; // 租金// 确定每种片子的租金switch(getPriceCode()){case Movie.REGULAR:result += 2;if(dayRented > 2 ){result += (dayRented - 2) * 1.5;}break;case Movie.NEW_RELEASE:result += dayRented*3;break;case Movie.CHILDRENS:result += 1.5;if(dayRented > 3 ){result += (dayRented - 3) * 1.5;}break;}return result;}/*** 常客积分计算* @param dayRented 租赁天数* @return*/int getFrequentRenterPoints(int dayRented){// (新片+租赁时间达2天 积分+1 )if(getPriceCode() == Movie.NEW_RELEASE && dayRented > 1){return 2;}else{return 1;}}}
2.1.2 修改Rental 类中的 getCharge ,getFrequentRenterPoints方法,让它调用Movie 类提供的新函数。
Rental 类中的计算租金方法和常客积分计算方法 改为:
/*** 常客积分计算* @return*/int getFrequentRenterPoints(){return _movie.getFrequentRenterPoints(_daysRented);}/*** 计算租金* @return*/double getCharge(){return _movie.getCharge(_daysRented);}
2.2 为了确保任何时候都要通过取值函数和赋值函数来访问 不愿意被外界直接访问的属性,我们用一个对赋值函数的调用来代替构造中的部分代码。
Movie 类的构造之前为:
private String _title;private int _priceCode;public Movie(String _title, int _priceCode) {this._title = _title;this._priceCode = _priceCode;}public int getPriceCode() {return _priceCode;}public void setPriceCode(int _priceCode) {this._priceCode = _priceCode;}public String getTitle() {return _title;}
让构造不能直接访问 不愿意被外界直接访问的属性,构造现在改为:
public Movie(String _title, int _priceCode) {this._title = _title;setPriceCode(_priceCode);}
2.3 有新需求到来,有新品种影片,租金计算又有新算法 。
用到设计模式的状态模式State(对象行为型)。
于是新建一个Price抽象类,并在其内给2个抽象方法用于获取影片的计价类型和计算租金,积分计算。
再写多个子类继承Price并各自实现父类方法以实现对租金计算,积分计算的重构。
2.3.1 Price 及子类 :
package bean;/*** 租金* @author Administrator*/
public abstract class Price {abstract int getPriceCode();
}
package bean;/*** 儿童片租金* @author Administrator*/
public class ChildrensPrice extends Price {@Overrideint getPriceCode() {return Movie.CHILDRENS;}}
package bean;public class NewReleasePrice extends Price {@Overrideint getPriceCode() {return Movie.NEW_RELEASE;}}
package bean;/*** 普通片租金* @author Administrator*/
public class RegularPrice extends Price {@Overrideint getPriceCode() {return Movie.REGULAR;}}
2.3.2 修改Movie 类的计价类型属性为Price类型,并改写赋值函数 :
package bean;/*** 影片* @author Administrator*/
public class Movie {public static final int CHILDRENS = 2; // 儿童片public static final int REGULAR = 0; // 普通片public static final int NEW_RELEASE = 1; // 新片private String _title;private Price _price; public Movie(String _title, int _priceCode) {this._title = _title;setPriceCode(_priceCode);}public int getPriceCode() {return _price.getPriceCode();}public void setPriceCode(int _priceCode) {switch(_priceCode){case REGULAR:_price = new RegularPrice();break;case NEW_RELEASE:_price = new NewReleasePrice();break;case CHILDRENS:_price = new ChildrensPrice();break;default:throw new IllegalArgumentException(" Incorrect PriceCode! ");}} ...
2.3.4 把租金计算方法移动到 Price 类,在Movie 类中调用即可。
package bean;/*** 租金* @author Administrator*/
public abstract class Price {abstract int getPriceCode();/*** 计算租金* @param dayRented 租赁天数* @return*/double getCharge(int dayRented){double result = 0; // 租金// 确定每种片子的租金switch(getPriceCode()){case Movie.REGULAR:result += 2;if(dayRented > 2 ){result += (dayRented - 2) * 1.5;}break;case Movie.NEW_RELEASE:result += dayRented*3;break;case Movie.CHILDRENS:result += 1.5;if(dayRented > 3 ){result += (dayRented - 3) * 1.5;}break;}return result;}
}
/*** 计算租金* @param dayRented 租赁天数* @return*/double getCharge(int dayRented){return _price.getCharge(dayRented); // 这是在 Movie 类中的调用}
2.3.5 重构租金计算方法,把每个getCharge 方法中switch 的每个 case 取出,在相应的Price子类中写一个覆盖函数。
最后把Price的 租金计算方法改为抽象方法。
租金类 Price 重构前:
package bean;/*** 租金* @author Administrator*/
public abstract class Price {abstract int getPriceCode();/*** 计算租金* @param dayRented 租赁天数* @return*/double getCharge(int dayRented){double result = 0; // 租金// 确定每种片子的租金switch(getPriceCode()){case Movie.REGULAR:result += 2;if(dayRented > 2 ){result += (dayRented - 2) * 1.5;}break;case Movie.NEW_RELEASE:result += dayRented*3;break;case Movie.CHILDRENS:result += 1.5;if(dayRented > 3 ){result += (dayRented - 3) * 1.5;}break;}return result;}
}
重构getCharge 方法后Price类 及子类 为:
package bean;/*** 租金* @author Administrator*/
public abstract class Price {abstract int getPriceCode();/*** 计算租金* @param dayRented 租赁天数* @return*/abstract double getCharge(int dayRented);}
package bean;/*** 儿童片租金* @author Administrator*/
public class ChildrensPrice extends Price {@Overrideint getPriceCode() {return Movie.CHILDRENS;}/*** 计算租金* @param dayRented 租赁天数* @return*/@Overridedouble getCharge(int dayRented){double result = 1.5;if(dayRented > 3){result += (dayRented - 3) * 1.5;}return result;}
}
package bean;
/*** 新片租金* @author Administrator*/
public class NewReleasePrice extends Price {@Overrideint getPriceCode() {return Movie.NEW_RELEASE;}/*** 计算租金* @param dayRented 租赁天数* @return*/@Overridedouble getCharge(int dayRented){return dayRented*3;}}
package bean;/*** 普通片租金* @author Administrator*/
public class RegularPrice extends Price {@Overrideint getPriceCode() {return Movie.REGULAR;}/*** 计算租金* @param dayRented 租赁天数* @return*/@Overridedouble getCharge(int dayRented){double result = 2;if(dayRented > 2){result += (dayRented - 2) * 1.5;}return result;}
}
2.3.6 对积分计算方法作相同重构。
从 Movie 类中移动积分计算方法到 Price 类中。Movie 类中调用Proce的积分计算方法就行了。
package bean;/*** 租金* @author Administrator*/
public abstract class Price {abstract int getPriceCode();/*** 计算租金* @param dayRented 租赁天数* @return*/abstract double getCharge(int dayRented);/*** 常客积分计算* @param dayRented 租赁天数* @return*/int getFrequentRenterPoints(int dayRented){// (新片+租赁时间达2天 积分+1 )if(getPriceCode() == Movie.NEW_RELEASE && dayRented > 1){return 2;}else{return 1;}}
}
package bean;/*** 影片* @author Administrator*/
public class Movie {public static final int CHILDRENS = 2; // 儿童片public static final int REGULAR = 0; // 普通片public static final int NEW_RELEASE = 1; // 新片private String _title;private Price _price; public Movie(String _title, int _priceCode) {this._title = _title;setPriceCode(_priceCode);}public int getPriceCode() {return _price.getPriceCode();}.../*** 常客积分计算* @param dayRented 租赁天数* @return*/int getFrequentRenterPoints(int dayRented){return _price.getFrequentRenterPoints(dayRented);}}
.../*** 常客积分计算* @param dayRented 租赁天数* @return*/int getFrequentRenterPoints(int dayRented){return _price.getFrequentRenterPoints(dayRented);}}
对 Proce 类的积分计算方法重构,只是为新片类型增加一个覆写函数,并在超类中保留原函数,使它成为一种默认行为。
package bean;/*** 租金* @author Administrator*/
public abstract class Price {abstract int getPriceCode();.../*** 常客积分计算* @param dayRented 租赁天数* @return*/int getFrequentRenterPoints(int dayRented){// 默认积1分return 1;}
}
package bean;
/*** 新片租金* @author Administrator*/
public class NewReleasePrice extends Price {.../*** 常客积分计算* @param dayRented 租赁天数* @return*/@Overrideint getFrequentRenterPoints(int dayRented){// (新片+租赁时间达2天 积分+1 )return (dayRented > 1) ? 2 : 1 ;}
}
.../*** 常客积分计算* @param dayRented 租赁天数* @return*/@Overrideint getFrequentRenterPoints(int dayRented){// (新片+租赁时间达2天 积分+1 )return (dayRented > 1) ? 2 : 1 ;}
}
到此,重构-改善既有代码的设计-第1例:租赁影片,就重构完成了。
总结 :这样重构以后,不论是修改影片分类结构,还是修改租金计算规则又或积分计算规则就都容易多了 。
注:个人觉得 Movie 类中的 setPriceCode 方法 中得每种 price 的时候不该用构造函数,而是该直接调用各Price 子类 中的 getPriceCode 方法。
但此博文尊重原书中代码未作改动。
最后 所有类完整代码为:
package bean;import java.util.Enumeration;
import java.util.Vector;/*** 顾客* @author Administrator*/
public class Customer{private String _name; // 顾客名字private Vector _rentals = new Vector(); // 租赁订单数组public Customer(String name) {super();this._name = name;}public void addRental(Rental arg){_rentals.addElement(arg);}public String getName() {return _name;}/*** 生成订单(打印凭条)* @return*/public String htmlStatement(){Enumeration rentals = _rentals.elements();String result = "<P><H1>Rentals for <EM> "+ getName() + "</EM></H1></P>\n";while( rentals.hasMoreElements()){Rental each = (Rental)rentals.nextElement();// 本次租赁记录说明result += each.getMovie().getTitle()+":"+ String.valueOf(each.getCharge())+"<BR>\n";}// 页脚result +="<P>You owe <EM>"+ String.valueOf(getTotalCharge())+"</EM></P>\n";result +="<P> on this rental you earned <EM> "+String.valueOf(getTotalFrequentRenterPoints())+"</EM> frequent renter points </P>";return result;}// 计算总积分private int getTotalFrequentRenterPoints(){int result = 0;Enumeration rentals = _rentals.elements();while(rentals.hasMoreElements()){Rental each = (Rental)rentals.nextElement();result += each.getFrequentRenterPoints();}return result;}// 计算总租金private double getTotalCharge(){double result = 0;Enumeration rentals = _rentals.elements();while(rentals.hasMoreElements()){Rental each = (Rental)rentals.nextElement();result += each.getCharge();}return result;}}
package bean;
/*** 租赁订单* @author Administrator*/
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;}/*** 常客积分计算* @return*/int getFrequentRenterPoints(){return _movie.getFrequentRenterPoints(_daysRented);}/*** 计算租金* @return*/double getCharge(){return _movie.getCharge(_daysRented);}}
package bean;/*** 影片* @author Administrator*/
public class Movie {public static final int CHILDRENS = 2; // 儿童片public static final int REGULAR = 0; // 普通片public static final int NEW_RELEASE = 1; // 新片private String _title;private Price _price; public Movie(String _title, int _priceCode) {this._title = _title;setPriceCode(_priceCode);}public int getPriceCode() {return _price.getPriceCode();}public void setPriceCode(int _priceCode) {switch(_priceCode){case REGULAR:_price = new RegularPrice();break;case NEW_RELEASE:_price = new NewReleasePrice();break;case CHILDRENS:_price = new ChildrensPrice();break;default:throw new IllegalArgumentException(" Incorrect PriceCode! ");}}public String getTitle() {return _title;}/*** 计算租金* @param dayRented 租赁天数* @return*/double getCharge(int dayRented){return _price.getCharge(dayRented);}/*** 常客积分计算* @param dayRented 租赁天数* @return*/int getFrequentRenterPoints(int dayRented){return _price.getFrequentRenterPoints(dayRented);}}
package bean;/*** 租金+积分* @author Administrator*/
public abstract class Price {abstract int getPriceCode();/*** 计算租金* @param dayRented 租赁天数* @return*/abstract double getCharge(int dayRented);/*** 常客积分计算* @param dayRented 租赁天数* @return*/int getFrequentRenterPoints(int dayRented){// 默认积1分return 1;}
}
package bean;/*** 儿童片租金* @author Administrator*/
public class ChildrensPrice extends Price {@Overrideint getPriceCode() {return Movie.CHILDRENS;}/*** 计算租金* @param dayRented 租赁天数* @return*/@Overridedouble getCharge(int dayRented){double result = 1.5;if(dayRented > 3){result += (dayRented - 3) * 1.5;}return result;}
}
package bean;
/*** 新片租金* @author Administrator*/
public class NewReleasePrice extends Price {@Overrideint getPriceCode() {return Movie.NEW_RELEASE;}/*** 计算租金* @param dayRented 租赁天数* @return*/@Overridedouble getCharge(int dayRented){return dayRented*3;}/*** 常客积分计算* @param dayRented 租赁天数* @return*/@Overrideint getFrequentRenterPoints(int dayRented){// (新片+租赁时间达2天 积分+1 )return (dayRented > 1) ? 2 : 1 ;}
}
package bean;/*** 普通片租金* @author Administrator*/
public class RegularPrice extends Price {@Overrideint getPriceCode() {return Movie.REGULAR;}/*** 计算租金* @param dayRented 租赁天数* @return*/@Overridedouble getCharge(int dayRented){double result = 2;if(dayRented > 2){result += (dayRented - 2) * 1.5;}return result;}
}