《重构-改善既有代码的设计》-第1例:租赁影片(2)
2017-04-22 13:52
369 查看
上接 重构-改善既有代码的设计-第1例:租赁影片(1)
2 运用多态取代与价格相关的条件逻辑
2.1 最好不要在另一个对象的属性基础上运用switch语句,应该在对象自己的数据上使用。
2.1.1 移动 getCharge ,getFrequentRenterPoints 方法到Movie 类中去。把会根据影片类型的变化而变化的东西放在影片类中。
Movie 类改为:
2.1.2 修改Rental 类中的 getCharge ,getFrequentRenterPoints方法,让它调用Movie 类提供的新函数。
Rental 类中的计算租金方法和常客积分计算方法 改为:
2.2 为了确保任何时候都要通过取值函数和赋值函数来访问 不愿意被外界直接访问的属性,我们用一个对赋值函数的调用来代替构造中的部分代码。
Movie 类的构造之前为:
让构造不能直接访问 不愿意被外界直接访问的属性,构造现在改为:
2.3 有新需求到来,有新品种影片,租金计算又有新算法 。
用到设计模式的状态模式State(对象行为型)。
于是新建一个Price抽象类,并在其内给2个抽象方法用于获取影片的计价类型和计算租金,积分计算。
再写多个子类继承Price并各自实现父类方法以实现对租金计算,积分计算的重构。
2.3.1 Price 及子类 :
2.3.2 修改Movie 类的计价类型属性为Price类型,并改写赋值函数 :
2.3.4 把租金计算方法移动到 Price 类,在Movie 类中调用即可。
2.3.5 重构租金计算方法,把每个getCharge 方法中switch 的每个 case 取出,在相应的Price子类中写一个覆盖函数。
最后把Price的 租金计算方法改为抽象方法。
租金类 Price 重构前:
重构getCharge 方法后Price类 及子类 为:
2.3.6 对积分计算方法作相同重构。
从 Movie 类中移动积分计算方法到 Price 类中。Movie 类中调用Proce的积分计算方法就行了。
对 Proce 类的积分计算方法重构,只是为新片类型增加一个覆写函数,并在超类中保留原函数,使它成为一种默认行为。
到此,重构-改善既有代码的设计-第1例:租赁影片,就重构完成了。
总结 :这样重构以后,不论是修改影片分类结构,还是修改租金计算规则又或积分计算规则就都容易多了 。
注:个人觉得 Movie 类中的 setPriceCode 方法 中得每种 price 的时候不该用构造函数,而是该直接调用各Price 子类 中的 getPriceCode 方法。
但此博文尊重原书中代码未作改动。
最后 所有类完整代码为:
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 { @Override int getPriceCode() { return Movie.CHILDRENS; } }
package bean; public class NewReleasePrice extends Price { @Override int getPriceCode() { return Movie.NEW_RELEASE; } }
package bean; /** * 普通片租金 * @author Administrator */ public class RegularPrice extends Price { @Override int 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 { @Override int getPriceCode() { return Movie.CHILDRENS; } /** * 计算租金 * @param dayRented 租赁天数 * @return */ @Override double 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 { @Override int getPriceCode() { return Movie.NEW_RELEASE; } /** * 计算租金 * @param dayRented 租赁天数 * @return */ @Override double getCharge(int dayRented){ return dayRented*3; } }
package bean; /** * 普通片租金 * @author Administrator */ public class RegularPrice extends Price { @Override int getPriceCode() { return Movie.REGULAR; } /** * 计算租金 * @param dayRented 租赁天数 * @return */ @Override double 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);
}
}
对 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 */ @Override int 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 { @Override int getPriceCode() { return Movie.CHILDRENS; } /** * 计算租金 * @param dayRented 租赁天数 * @return */ @Override double 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 { @Override int getPriceCode() { return Movie.NEW_RELEASE; } /** * 计算租金 * @param dayRented 租赁天数 * @return */ @Override double getCharge(int dayRented){ return dayRented*3; } /** * 常客积分计算 * @param dayRented 租赁天数 * @return */ @Override int getFrequentRenterPoints(int dayRented){ // (新片+租赁时间达2天 积分+1 ) return (dayRented > 1) ? 2 : 1 ; } }
package bean; /** * 普通片租金 * @author Administrator */ public class RegularPrice extends Price { @Override int getPriceCode() { return Movie.REGULAR; } /** * 计算租金 * @param dayRented 租赁天数 * @return */ @Override double getCharge(int dayRented){ double result = 2; if(dayRented > 2){ result += (dayRented - 2) * 1.5; } return result; } }
相关文章推荐
- 《重构-改善既有代码的设计》-第1例:租赁影片(1)
- 《重构-改善既有代码的设计》读书笔记(一)
- 重构:改善既有代码的设计(软件开发的不朽经典)
- 重构-改善既有代码的设计总结
- 重构-改善既有代码的设计Refactoring - Improving the Design of Existing Code
- 重构-改善既有代码的设计
- 《重构--改善既有代码的设计 》
- 笔记-重构-改善既有代码的设计(Refactoring - Improving the Design of Existing Code)
- 学习《重构-改善既有代码的设计》一
- 《重构-改善既有代码的设计》清单
- 重构-改善既有代码的设计
- 《重构-改善既有代码的设计》笔记1
- 读重构-改善既有代码的设计笔记
- 《重构-改善既有代码的设计》 读书心得
- 《重构-改善既有的代码设计》读书笔记
- 《重构-改善代码既有的设计》英文目录
- 谁有《重构-改善既有代码的设计》电子版
- 《重构—改善既有代码设计》——第二章重构原则——学习笔记
- 重构-改善既有代码的设计——第一章 重构,第一个案例
- 《重构——改善现有代码的设计》 读书笔记