示例:影片出租店程序(重构——引入状态模式)
由于考虑到“影片分类结构”、“费用计算规则”或“常客积点计算规则”在未来可能会发生改变,所以通过引入“状态模式”将“费用计算”和“常客积点计算”中“因条件而异的代码”替换掉。
步骤:
1、移动“金额计算”方法 —— “搬移方法(Move Method)”
由于Rental类的GetCharge方法中使用到了Movie类的属性,所以这暗示应将GetCharge方法移动到Movie类中。
Eclipse工具重构步骤:
(1)、选中“方法名”,右键选择“Move”
(3)、调整代码如下
public class Movie {
…………
double getCharge(Rental rental) { //--计算一笔租片费用
double result = 0;
switch(rental.get_movie().get_priceCode()){ //--取得影片出租价格
case Movie.REGULAR: //--普通片
result += 2;
if (rental.get_daysRented() > 2)
result += (rental.get_daysRented() - 2) * 1.5;
break;
case Movie.NEW_RELEASE: //--新片
result += rental.get_daysRented() * 3;
break;
case Movie.CHILDRENS: //--儿童片
result += 1.5;
if (rental.get_daysRented() > 3)
result += (rental.get_daysRented() - 3) * 1.5;
break;
}
return result;
}
}
public class Rental {
…………
double getCharge() {
return _movie.getCharge(this);
}
}
(4)、选中代码,右键选择“Extract Local Variable”
(4)、调整代码如下
public class Movie {
…………
double getCharge(int daysRented) { //--计算一笔租片费用
double result = 0;
switch(get_priceCode()){ //--取得影片出租价格
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;
}
}
public class Rental {
…………
double getCharge() {
return _movie.getCharge(get_daysRented());
}
}
2、移动“常客积点计算”方法 —— “搬移方法(Move Method)”
public class Movie {
…………
int getFrequentRenterPoints(int daysRented) {
if (get_priceCode() == Movie.NEW_RELEASE && daysRented > 1)
return 2;
else
return 1;
}
}
public class Rental {
…………
int getFrequentRenterPoints() {
return _movie.getFrequentRenterPoints(get_daysRented());
}
}
3、重构“费用计算”方法 —— 引入状态模式
运用“状态模式”取代与价格相关的条件逻辑。
3.1、替换“价格类型码”——“替换类型码(Replace Type Code with State/Strategy)”
运用“替换类型码” 将“与型别相依的行为”搬移至“状态类”内。
1、封装“价格字段”——“封装字段(Self Encapsulate Field)”
第一步是针对“与型别相依的行为”使用“封装字段” ,确保任何时候都通过getting和setting两个函数来运用这些行为。
public class Movie {
…………
public Movie(String title, int priceCode) {
_title = title;
set_priceCode(priceCode);
}
}
2、创建“状态类”
第二步是实现price类,并在其中提供“与型别相依的行为”。
Eclipse工具重构步骤:
(1)、选中类,右键选择“Override/Implement Methods”
(3)、调整代码如下
abstract class Price {
abstract int get_priceCode(); //--取得价格代号
}
class ChildrensPrice extends Price{
int get_priceCode() {
return Movie.CHILDRENS;
}
}
class NewReleasePrice extends Price{
int get_priceCode() {
return Movie.NEW_RELEASE;
}
}
class RegularPrice extends Price{
int get_priceCode() {
return Movie.REGULAR;
}
}
3、修改“价格代号”访问函数
第三步,修改Movie类内的“价格代号”访问函数, 让它们使用Price类。
public class Movie {
private Price _price; //价格
public int get_priceCode() { //--取得价格代码
return _price.get_priceCode();
}
public void set_priceCode(int arg) {
switch(arg){
case REGULAR: //--普通片
_price = new RegularPrice();
break;
case CHILDRENS: //--儿童片
_price = new ChildrensPrice();
break;
case NEW_RELEASE: //--新片
_price = new NewReleasePrice();
break;
default:
throw new IllegalArgumentException("Incorrect PriceCode");
}
}
}
3.2、搬移“费用计算”方法 ——“搬移方法(Move Method)”
对Movie.GetCharge运用“搬移方法”将switch语句移到Price类里头。
abstract class Price {
abstract int get_priceCode(); //--取得价格代号
double getCharge(int daysRented) { //--计算一笔租片费用
double result = 0;
switch(get_priceCode()){ //--取得影片出租价格
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;
}
}
public class Movie {
double getCharge(int daysRented) {
return _price.getCharge(daysRented);
}
}
3.3、替换switch语句——“用多态替换条件语句(Replace Condional with Polymorphism)”
最后运用“用多态替换条件语句”去掉Price.GetCharge方法中的switch语句。
1、提取Movie.REGULAR分支
作法是每次取出一个case分支,在相应的 class 内建立一个“覆写方法(overriding method)”。
先从RegularPrice类开始,RegularPrice.GetCharge方法覆写了父类中的case语句。
class RegularPrice extends Price{
int get_priceCode() {
return Movie.REGULAR;
}
double getCharge(int daysRented) {
double result = 2;
if (daysRented > 2)
result += (daysRented - 2) * 1.5;
return result;
}
}
2、提取Movie.NEW_RELEASE分支
class NewReleasePrice extends Price{
int get_priceCode() {
return Movie.NEW_RELEASE;
}
double getCharge(int daysRented) {
return daysRented * 3;
}
}
3、提取Movie.CHILDRENS分支
class ChildrensPrice extends Price{
int get_priceCode() {
return Movie.CHILDRENS;
}
double getCharge(int daysRented) {
double result = 1.5;
if (daysRented > 3)
result += (daysRented - 3) * 1.5;
return result;
}
}
4、处理Price.GetCharge
处理完所有case分支之后,把Price.GetCharge声明为abstract。
abstract class Price {
abstract int get_priceCode(); //--取得价格代号
abstract double getCharge(int daysRented); //--计算一笔租片费用
}
4、重构“常客积点计算”方法 —— 引入状态模式
运用“状态模式”取代Movie.GetFrequentRenterPoints方法中“与型别相依的行为”,也就是“判断是否为新片self.PriceCode = NEW_RELEASE”那个动作。
4.1、搬移“常客积点计算”方法 ——“搬移方法(Move Method)”
abstract class Price {
int getFrequentRenterPoints(int daysRented) {
if (get_priceCode() == Movie.NEW_RELEASE && daysRented > 1)
return 2;
else
return 1;
}
}
public class Movie {
int getFrequentRenterPoints(int daysRented) {
return _price.getFrequentRenterPoints(daysRented);
}
}
4.2、替换条件语句——“用多态替换条件语句(Replace Condional with Polymorphism)”
不需声明TPrice.GetFrequentRenterPoints函数为abstract,使其成为一种缺省行为。只为“新片类型”产生一个覆写函数。
abstract class Price {
int getFrequentRenterPoints(int daysRented) {
return 1;
}
}
class NewReleasePrice extends Price{
int getFrequentRenterPoints(int daysRented) {
return daysRented > 1 ? 2 : 1;
}
}
代码:
1、Customer.java
package movie_Ref1;
import java.util.Enumeration;
import java.util.Vector;
public class Customer {
private String _name;//姓名
private Vector _rentals = new Vector(); //租借记录
public Customer(String name) {
_name = name;
}
public void addRental(Rental obj) {
_rentals.addElement(obj);
}
public String get_name() {
return _name;
}
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;
}
public String statement() {
Enumeration rentals = _rentals.elements();
String result = "Rental Record for " + get_name() + "/n";
while(rentals.hasMoreElements()){
Rental each = (Rental) rentals.nextElement(); //--取得一笔租借记录
//---显示此笔租借数据
result += "/t" + each.get_movie().get_title() + "/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;
}
public String htmlStatement() {
Enumeration rentals = _rentals.elements();
String result = "
Rentals for " + get_name() + "
/n";
while(rentals.hasMoreElements()){
Rental each = (Rental) rentals.nextElement(); //--取得一笔租借记录
//---显示此笔租借数据
result += each.get_movie().get_title() + ": " +
String.valueOf(each.getCharge()) + "
/n";
}
//---结尾打印
result += "
You owe " + String.valueOf(getTotalCharge()) + "
/n";
result += "On this rental you earned " + String.valueOf(getTotalFrequentRenterPoints()) +
" frequent renter points
";
return result;
}
}
2、Movie.java
package movie_Ref1;
/**
* 影片
*
*/
public class Movie {
public static final int REGULAR = 0;
public static final int NEW_RELEASE = 1;
public static final int CHILDRENS = 2;
private String _title; //名称
private Price _price; //价格
public Movie(String title, int priceCode) {
_title = title;
set_priceCode(priceCode);
}
public String get_title() {
return _title;
}
public int get_priceCode() { //--取得价格代码
return _price.get_priceCode();
}
public void set_priceCode(int arg) {
switch(arg){
case REGULAR:
_price = new RegularPrice();
break;
case CHILDRENS:
_price = new ChildrensPrice();
break;
case NEW_RELEASE:
_price = new NewReleasePrice();
break;
default:
throw new IllegalArgumentException("Incorrect PriceCode");
}
}
double getCharge(int daysRented) {
return _price.getCharge(daysRented);
}
int getFrequentRenterPoints(int daysRented) {
return _price.getFrequentRenterPoints(daysRented);
}
}
3、Rental.java
package movie_Ref1;
/**
* 租赁
*
*/
public class Rental {
private Movie _movie; //影片
private int _daysRented; //租期
public Rental(Movie movie, int daysRented) {
_movie = movie;
_daysRented = daysRented;
}
public int get_daysRented() {
return _daysRented;
}
public Movie get_movie() {
return _movie;
}
double getCharge() {
return _movie.getCharge(get_daysRented());
}
int getFrequentRenterPoints() {
return _movie.getFrequentRenterPoints(get_daysRented());
}
}
4、Price.java
package movie_Ref1;
abstract class Price {
abstract int get_priceCode(); //--取得价格代号
abstract double getCharge(int daysRented); //--计算一笔租片费用
int getFrequentRenterPoints(int daysRented) {
return 1;
}
}
class ChildrensPrice extends Price{
int get_priceCode() {
return Movie.CHILDRENS;
}
double getCharge(int daysRented) {
double result = 1.5;
if (daysRented > 3)
result += (daysRented - 3) * 1.5;
return result;
}
}
class NewReleasePrice extends Price{
int get_priceCode() {
return Movie.NEW_RELEASE;
}
double getCharge(int daysRented) {
return daysRented * 3;
}
int getFrequentRenterPoints(int daysRented) {
return daysRented > 1 ? 2 : 1;
}
}
class RegularPrice extends Price{
int get_priceCode() {
return Movie.REGULAR;
}
double getCharge(int daysRented) {
double result = 2;
if (daysRented > 2)
result += (daysRented - 2) * 1.5;
return result;
}
}