1.1重構-第一章

重構是在不改變軟件可觀察行爲的前提下改善其內部結構。

設計模式爲重構提供了目標

1.1 起點

這是一個影片出租店用的程序,計算每一個顧客的消費金額並打印詳單。操作者告訴程序:租客租了哪些影片租期多長,程序便根據租賃時間和影片類型算出費用。影片分爲三類:普通片、兒童片、新片。除了計算費用,還要爲常客計算積分,積分會根據租片種類是否爲新片而有不同。


Movie(影片)- movie只是一個簡單的純數據類

/**
 * 影片
 * @author harry
 */
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 int _priceCode;
	
	public Movie(String _title, int _priceCode) {
		super();
		this._title = _title;
		this._priceCode = _priceCode;
	}
	
	public String get_title() {
		return _title;
	}
	public void set_title(String _title) {
		this._title = _title;
	}
	public int get_priceCode() {
		return _priceCode;
	}
	public void set_priceCode(int _priceCode) {
		this._priceCode = _priceCode;
	}
}
Rental(租賃)- rental表示某個顧客租了一部影片

/**
 * 租賃實體
 * @author harry
 */
public class Rental {
	private Movie _movie;
	private int _dayRented;
	
	public Rental(Movie _movie, int _dayRented) {
		super();
		this._movie = _movie;
		this._dayRented = _dayRented;
	}
	
	public Movie get_movie() {
		return _movie;
	}
	public void set_movie(Movie _movie) {
		this._movie = _movie;
	}
	public int get_dayRented() {
		return _dayRented;
	}
	public void set_dayRented(int _dayRented) {
		this._dayRented = _dayRented;
	}
}
Customer(顧客)-customer表示顧客。與其他類一樣,它也擁有數據和相應的訪問函數

/**
 * 顧客實體
 * @author harry
 */
public class Customer {
	private String _name;
	private Vector<Rental> _rentals = new Vector<>();
	public Customer(String _name) {
		super();
		this._name = _name;
	}
	
	public void addRental(Rental arg){
		_rentals.addElement(arg);
	}
	public String getName() {
		return _name;
	}
	public void setName(String _name) {
		this._name = _name;
	}
	// 生成詳情單
	public String statement(){
		double totalAmount = 0;//總金額
		int frequentRenterPoints = 0;//本次總積分
		
		Enumeration<Rental> rentals = _rentals.elements();
		// 租賃備案
		String result = "Rental Record for "+getName()+"\n";
		while(rentals.hasMoreElements()){
			double thisAmount = 0;
			Rental each = rentals.nextElement();
			
			// 計算金額
			switch(each.get_movie().get_priceCode()){
				case Movie.REGULAR:
					thisAmount += 2;
					if (each.get_dayRented() > 2) {
						thisAmount += (each.get_dayRented()-2)*1.5;
					}
					break;
				case Movie.NEW_RELEASE:
					thisAmount += each.get_dayRented()*3;
					break;
				case Movie.CHILDRENS:
					thisAmount += 1.5;
					if (each.get_dayRented() > 3) {
						thisAmount += (each.get_dayRented()-3)*1.5;
					}
					break;
			}
			
			// 常規積分累加
			frequentRenterPoints++;
			// 特殊新書積分計算
			if (each.get_movie().get_priceCode() == Movie.NEW_RELEASE &&
				each.get_dayRented() > 1) {
				frequentRenterPoints++;
			}
			
			// 顯示憑條
			result += "\t"+each.get_movie().get_title()+"\t"+String.valueOf(thisAmount)+"\n";
			totalAmount += thisAmount;
		}
		
		// 組裝頁腳
		result += "Amount owed is "+String.valueOf(totalAmount)+"\n";
		result += "You earned "+String.valueOf(frequentRenterPoints)+" frequent renter points";
		return result;
	}
}

對起始程序的評價

         Customer裏的長長的statement()做的事情太多了,它做了原本應該由其他類完成的事情。

         在這個例子中,如果用戶希望以HTML格式輸出詳情單。上述程序中的statement()無法複用,唯一可以做就是編寫一個全新的htmlStatement()。大量重複statement()的行爲。當然這個還不是太費力,你可以把statement()複製一份按需求修改就可以了。

         但是如果計費標準發生變化,又會如何呢?你必須同時修改statement()和htmlStatement(),並確保兩處修改一致。當你後續還要修改的時候,複製粘貼帶來的問題就會浮現出來。如果你編寫的是一個永遠不會修改的程序,那麼剪剪粘粘就還好,但如果程序要保存很長時間,而且可能需要修改,複製粘貼行爲就會造成潛在威脅。

         現在第二個變化來了:用戶希望改變影片分類規則,但是還沒有決定怎麼改。他們設想了幾種方案,這些方案都會影響顧客消費和常客積分點的計算方式,作爲一個經驗豐富的開發者,你可以肯定:不論用戶提出什麼方案,你唯一能夠獲得的保證就是他們一定會在六個月之內再次修改它。

         爲了應付分類規則和計費規則的變化,程序必須對statement()做出修改,但如果我們把statement()內的代碼複製到用以打印HTML詳單的函數中,就必須確保將來在任何修改這兩個地方保持一致,隨着各種規則變的越來越複雜,適當的修改點越來越難,不犯錯的機會越來越少。

         接下來重構技術就該粉墨登場了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章