前些天發現了一個巨牛的人工智能學習網站,通俗易懂,風趣幽默,忍不住分享一下給大家。點擊跳轉到教程。
上接 ?重構-改善既有代碼的設計-第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;}
}
?
?
?
?
?
?
?
?
?