前些天發現了一個巨牛的人工智能學習網站,通俗易懂,風趣幽默,忍不住分享一下給大家。點擊跳轉到教程。
買了《重構 - 改善既有代碼的設計?》一書,一直沒有好好看,大致過了下也覺得只是有點點印象而已,最后還是決定把代碼敲一次,記錄一下這些學習過程。
?
第一例:租賃影片
程序說明:顧客租了哪些影片,租期多長,根據租賃時間和影片類型算出費用和積分。
1. 分解并重組statement()?
原代碼如下有3個類。?
?
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;}}
?
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;}}
?
?
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 statement(){double totalAmount = 0; // 總租金 int frequentRenterPoints = 0; // 積分Enumeration rentals = _rentals.elements();String result = "Rental Record for "+ getName() + "\n";while( rentals.hasMoreElements()){double thisAmount = 0; // 租金Rental each = (Rental)rentals.nextElement();// 確定每種片子的租金switch(each.getMovie().getPriceCode()){case Movie.REGULAR:thisAmount += 2;if(each.getDaysRented() > 2 ){thisAmount += (each.getDaysRented() - 2) * 1.5;}break;case Movie.NEW_RELEASE:thisAmount += each.getDaysRented()*3;break;case Movie.CHILDRENS:thisAmount += 1.5;if(each.getDaysRented() > 3 ){thisAmount += (each.getDaysRented() - 3) * 1.5;}break;}// 增加積分frequentRenterPoints ++;// 新片+租賃時間達2天 積分+1 if(each.getMovie().getPriceCode() == Movie.NEW_RELEASE && each.getDaysRented() > 1){frequentRenterPoints ++;}// 本次租賃記錄說明result += "\t"+each.getMovie().getTitle()+"\t"+ String.valueOf(thisAmount)+"\n";totalAmount += thislAmount;}// 頁腳result +="Amount owed is "+ String.valueOf(totalAmount)+"\n";result +="You eared "+String.valueOf(frequentRenterPoints)+"frequent renter points";return result;}}
1.1 抽離switch 語句到獨立方法
1.1.1 Customer類改為:
?
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 statement(){double totalAmount = 0; // 總租金 int frequentRenterPoints = 0; // 積分Enumeration rentals = _rentals.elements();String result = "Rental Record for "+ getName() + "\n";while( rentals.hasMoreElements()){double thisAmount = 0; // 租金Rental each = (Rental)rentals.nextElement();thisAmount = amountFor(each); // 計算租金// 增加積分frequentRenterPoints ++; // 新片+租賃時間達2天 積分+1 if(each.getMovie().getPriceCode() == Movie.NEW_RELEASE && each.getDaysRented() > 1){frequentRenterPoints ++;}// 本次租賃記錄說明result += "\t"+each.getMovie().getTitle()+"\t"+ String.valueOf(thisAmount)+"\n";totalAmount += thisAmount;}// 頁腳result +="Amount owed is "+ String.valueOf(totalAmount)+"\n";result +="You eared "+String.valueOf(frequentRenterPoints)+"frequent renter points";return result;}// 計算租金private int amountFor(Rental each){int thisAmount = 0; // 租金// 確定每種片子的租金switch(each.getMovie().getPriceCode()){case Movie.REGULAR:thisAmount += 2;if(each.getDaysRented() > 2 ){thisAmount += (each.getDaysRented() - 2) * 1.5;}break;case Movie.NEW_RELEASE:thisAmount += each.getDaysRented()*3;break;case Movie.CHILDRENS:thisAmount += 1.5;if(each.getDaysRented() > 3 ){thisAmount += (each.getDaysRented() - 3) * 1.5;}break;}return thisAmount;}}
?
?
1.1.2 注意?amountFor 方法的返回類型應該是double類型:
?Customer類改為:
?
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 statement(){double totalAmount = 0; // 總租金 int frequentRenterPoints = 0; // 積分Enumeration rentals = _rentals.elements();String result = "Rental Record for "+ getName() + "\n";while( rentals.hasMoreElements()){double thisAmount = 0; // 租金Rental each = (Rental)rentals.nextElement();thisAmount = amountFor(each); // 計算租金// 增加積分frequentRenterPoints ++; // 新片+租賃時間達2天 積分+1 if(each.getMovie().getPriceCode() == Movie.NEW_RELEASE && each.getDaysRented() > 1){frequentRenterPoints ++;}// 本次租賃記錄說明result += "\t"+each.getMovie().getTitle()+"\t"+ String.valueOf(thisAmount)+"\n";totalAmount += thisAmount;}// 頁腳result +="Amount owed is "+ String.valueOf(totalAmount)+"\n";result +="You eared "+String.valueOf(frequentRenterPoints)+"frequent renter points";return result;}// 計算租金private double amountFor(Rental each){double thisAmount = 0; // 租金// 確定每種片子的租金switch(each.getMovie().getPriceCode()){case Movie.REGULAR:thisAmount += 2;if(each.getDaysRented() > 2 ){thisAmount += (each.getDaysRented() - 2) * 1.5;}break;case Movie.NEW_RELEASE:thisAmount += each.getDaysRented()*3;break;case Movie.CHILDRENS:thisAmount += 1.5;if(each.getDaysRented() > 3 ){thisAmount += (each.getDaysRented() - 3) * 1.5;}break;}return thisAmount;}}
1.3 ?變量名稱應該見名知意:好的代碼應該清楚的表達出自己的功能,變量名稱是代碼清晰的一個關鍵。
?
amountFor方法中局部變量 thisAmount , 參數each 要改名。
改變量名 ,?Customer類改為:
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 statement(){double totalAmount = 0; // 總租金 int frequentRenterPoints = 0; // 積分Enumeration rentals = _rentals.elements();String result = "Rental Record for "+ getName() + "\n";while( rentals.hasMoreElements()){double thisAmount = 0; // 租金Rental each = (Rental)rentals.nextElement();thisAmount = amountFor(each); // 計算租金// 增加積分frequentRenterPoints ++; // 新片+租賃時間達2天 積分+1 if(each.getMovie().getPriceCode() == Movie.NEW_RELEASE && each.getDaysRented() > 1){frequentRenterPoints ++;}// 本次租賃記錄說明result += "\t"+each.getMovie().getTitle()+"\t"+ String.valueOf(thisAmount)+"\n";totalAmount += thisAmount;}// 頁腳result +="Amount owed is "+ String.valueOf(totalAmount)+"\n";result +="You eared "+String.valueOf(frequentRenterPoints)+"frequent renter points";return result;}// 計算租金private double amountFor(Rental aRental){double result = 0; // 租金// 確定每種片子的租金switch(aRental.getMovie().getPriceCode()){case Movie.REGULAR:result += 2;if(aRental.getDaysRented() > 2 ){result += (aRental.getDaysRented() - 2) * 1.5;}break;case Movie.NEW_RELEASE:result += aRental.getDaysRented()*3;break;case Movie.CHILDRENS:result += 1.5;if(aRental.getDaysRented() > 3 ){result += (aRental.getDaysRented() - 3) * 1.5;}break;}return result;}}
?
?
1.4 函數應該放在它所使用的數據所屬的對象中, 顧客租金的計算應該移動到Rental類中去。
?
?
?
Rental 類改為 : ?(計算租金方法去掉參數并改名 )
?
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;}// 計算租金double getCharge(){double result = 0; // 租金// 確定每種片子的租金switch(getMovie().getPriceCode()){case Movie.REGULAR:result += 2;if(getDaysRented() > 2 ){result += (getDaysRented() - 2) * 1.5;}break;case Movie.NEW_RELEASE:result += getDaysRented()*3;break;case Movie.CHILDRENS:result += 1.5;if(getDaysRented() > 3 ){result += (getDaysRented() - 3) * 1.5;}break;}return result;}}
原顧客類中amountFor方法直接調用計算租金方法即可,Customer 類改為:
?
?
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 statement(){double totalAmount = 0; // 總租金 int frequentRenterPoints = 0; // 積分Enumeration rentals = _rentals.elements();String result = "Rental Record for "+ getName() + "\n";while( rentals.hasMoreElements()){double thisAmount = 0; // 租金Rental each = (Rental)rentals.nextElement();thisAmount = amountFor(each); // 計算租金// 增加積分frequentRenterPoints ++; // 新片+租賃時間達2天 積分+1 if(each.getMovie().getPriceCode() == Movie.NEW_RELEASE && each.getDaysRented() > 1){frequentRenterPoints ++;}// 本次租賃記錄說明result += "\t"+each.getMovie().getTitle()+"\t"+ String.valueOf(thisAmount)+"\n";totalAmount += thisAmount;}// 頁腳result +="Amount owed is "+ String.valueOf(totalAmount)+"\n";result +="You eared "+String.valueOf(frequentRenterPoints)+"frequent renter points";return result;}// 計算租金private double amountFor(Rental aRental){return aRental.getCharge();}}
1.5 ?去掉舊函數amountFor,直接調用新函數getCharge。Customer類改為 :?
?
?
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 statement(){double totalAmount = 0; // 總租金 int frequentRenterPoints = 0; // 積分Enumeration rentals = _rentals.elements();String result = "Rental Record for "+ getName() + "\n";while( rentals.hasMoreElements()){double thisAmount = 0; // 租金Rental each = (Rental)rentals.nextElement();thisAmount = each.getCharge(); // 計算租金// 增加積分frequentRenterPoints ++; // 新片+租賃時間達2天 積分+1 if(each.getMovie().getPriceCode() == Movie.NEW_RELEASE && each.getDaysRented() > 1){frequentRenterPoints ++;}// 本次租賃記錄說明result += "\t"+each.getMovie().getTitle()+"\t"+ String.valueOf(thisAmount)+"\n";totalAmount += thisAmount;}// 頁腳result +="Amount owed is "+ String.valueOf(totalAmount)+"\n";result +="You eared "+String.valueOf(frequentRenterPoints)+"frequent renter points";return result;}}
1.6 ?盡量去掉臨時變量,臨時變量會導致大量參數的傳遞,沒有必要。 thisAmount 是個多余的臨時變量,直接去掉。
?
Customer?改為:
?
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 statement(){double totalAmount = 0; // 總租金 int frequentRenterPoints = 0; // 積分Enumeration rentals = _rentals.elements();String result = "Rental Record for "+ getName() + "\n";while( rentals.hasMoreElements()){Rental each = (Rental)rentals.nextElement();// 增加積分frequentRenterPoints ++; // 新片+租賃時間達2天 積分+1 if(each.getMovie().getPriceCode() == Movie.NEW_RELEASE && each.getDaysRented() > 1){frequentRenterPoints ++;}// 本次租賃記錄說明result += "\t"+each.getMovie().getTitle()+"\t"+ String.valueOf(each.getCharge())+"\n";totalAmount += each.getCharge();}// 頁腳result +="Amount owed is "+ String.valueOf(totalAmount)+"\n";result +="You eared "+String.valueOf(frequentRenterPoints)+"frequent renter points";return result;}}
?
1.7 把積分計算方法放到Rental類中,寫為常客積分計算方法(getFrequentRenterPoints),并改變Customer類中積分計算代碼 。
?
Rental類改為:
?
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;}// 計算租金double getCharge(){double result = 0; // 租金// 確定每種片子的租金switch(getMovie().getPriceCode()){case Movie.REGULAR:result += 2;if(getDaysRented() > 2 ){result += (getDaysRented() - 2) * 1.5;}break;case Movie.NEW_RELEASE:result += getDaysRented()*3;break;case Movie.CHILDRENS:result += 1.5;if(getDaysRented() > 3 ){result += (getDaysRented() - 3) * 1.5;}break;}return result;}// 常客積分計算int getFrequentRenterPoints(){// 增加積分int frequentRenterPoints =0;frequentRenterPoints++; // (新片+租賃時間達2天 積分+1 )if(getMovie().getPriceCode() == Movie.NEW_RELEASE && getDaysRented() > 1){frequentRenterPoints ++;}return frequentRenterPoints;}}
Customer類中statement方法改為:
?
?
/*** 生成訂單* @return*/public String statement(){double totalAmount = 0; // 總租金 int frequentRenterPoints = 0; // 積分Enumeration rentals = _rentals.elements();String result = "Rental Record for "+ getName() + "\n";while( rentals.hasMoreElements()){Rental each = (Rental)rentals.nextElement();// 積分frequentRenterPoints += each.getFrequentRenterPoints();// 本次租賃記錄說明result += "\t"+each.getMovie().getTitle()+"\t"+ String.valueOf(each.getCharge())+"\n";totalAmount += each.getCharge();}// 頁腳result +="Amount owed is "+ String.valueOf(totalAmount)+"\n";result +="You eared "+String.valueOf(frequentRenterPoints)+"frequent renter points";return result;}
?
1.8 去掉statement方法中的2個臨時變量:totalAmount 和?frequentRenterPoints 。抽離出對應計算方法,并調用。
?
Customer?類改為:
?
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 statement(){Enumeration rentals = _rentals.elements();String result = "Rental Record for "+ getName() + "\n";while( rentals.hasMoreElements()){Rental each = (Rental)rentals.nextElement();// 本次租賃記錄說明result += "\t"+each.getMovie().getTitle()+"\t"+ String.valueOf(each.getCharge())+"\n";}// 頁腳result +="Amount owed is "+ String.valueOf(getTotalCharge())+"\n";result +="You eared "+String.valueOf(getTotalFrequentRenterPoints())+"frequent renter points";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;}}
?
本步改寫作用說明:
?
?這里雖然從1個循環變為3個,但是多了2個查詢函數。
1) ?使得Customer 類中的任何代碼都可以調用這些查詢函數。
2) ?若系統其它部分需要這些信息,也可以輕松地將查詢函數加入?Customer 類接口。而若沒有這些查詢波函數,其它函數就必須了解 ?Rental 類,并自行建立循環。
?
?
1.9 加功能:打印憑條。
statement 方法改為 htmlStatement :
?
?
/*** 生成訂單(打印憑條)* @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;}
?
?
?
?
?
未完,見 ?:?重構-改善既有代碼的設計-第1例:租賃影片(2)
?
?
?
?
?
?
?
?