【《重構 改善既有代碼的設計》學習筆記1】重構:第一個案例
【《重構 改善既有代碼的設計》學習筆記】重構:第一個案例
本篇文章的內容來自《重構 改善既有代碼的設計》一書學習筆記整理筆記並且加上自己的淺顯的思考總結!
一、簡單的例子
一個影片出租店用的程序,計算每一位顧客的消費金額,並打印詳單。
詳單打印 顧客租了哪些影片、租期多長,影片類型 、單個影片費用、總費用 。 除了費用外,還要計算顧客的積分,不同種類租片積分不同。
註:影片為為三類:普通片、兒童片、新片!
Think:如果是你做這樣一個功能,用java編寫,你會怎麽去寫代碼?可以簡單思考一下整個邏輯在往下面看!
簡單點設計三個表我,一個顧客表,一個影片表,還有一個是租用記錄表。那麽在java中顧客這個類是要有的 Customer 、然後影片這個類要有 Movie ,還有一個租期類 Rental。
1、三個類組合實現一個打印詳單的功能
摘錄原書 的代碼 ,稍做字段調整 。Customer
類如下,有一個 statement()
打印詳單!
/** * 顧客 * * @author:dufy * @version:1.0.0 * @date 2019/1/21 */ public class Customer { private String _name; private Vector rentals = new Vector(); public Coustomer(String _name) { this._name = _name; } public void addRental(Rental rental) { rentals.addElement(rental); } public String getName() { return _name; } public String statement() { //總費用 double totalAmount = 0; // 積分數 int integralNum = 0; Enumeration erts = rentals.elements(); String result = "租用的顧客名字為: " + getName() + "\n"; result += "=========================================\n"; while (erts.hasMoreElements()) { // 每個影片的費用 double thisAmount = 0; Rental rental = (Rental) erts.nextElement(); int daysRented = rental.getDaysRented(); Integer type = rental.getMovie().getType(); switch (type) { case Movie.REGULAR: thisAmount += 2; if (daysRented > 2) { thisAmount += (daysRented - 2) * 1.5; } break; case Movie.NEW_RELEASE: thisAmount += daysRented * 3; break; case Movie.CHILDRENS: thisAmount += 1.5; if (daysRented > 3) { thisAmount += (daysRented - 3) * 1.5; } break; } integralNum++; // 如果是租用新片則累積多加1積分 if (type == Movie.NEW_RELEASE && daysRented > 1) { integralNum++; } //打印租用影片詳情 result += "影片名稱:" + rental.getMovie().getTitle() + "\t 影片類型:" + rental.getMovie().getMovieTypeName(type) + "\n"; result += "租期:" + daysRented + "天\t 費用:" + thisAmount + "元\n"; result += "----------------------------------------------------\n"; totalAmount += thisAmount; } result += ">>>>>>>>>>>>>>>>>>總費用:" + totalAmount + "元<<<<<<<<<<<<<<<<<<<< \n"; result += ">>>>>>>>>>>>>>>>>>本次積分:" + integralNum + "<<<<<<<<<<<<<<<<<<<<"; return result; } }
Movie
類的代碼
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 Integer type; public Movie() { } public Movie(String title, Integer type) { this.title = title; this.type = type; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public Integer getType() { return type; } public void setType(Integer type) { this.type = type; } public String getMovieTypeName(int type){ String name = ""; if(REGULAR == type){ name = "普通片"; }else if(NEW_RELEASE == type){ name = "新片"; }else if(CHILDRENS == type){ name = "兒童片"; }else{ name = "未知"; } return name; } }
Rental
類代碼:
public class Rental {
/**
* 影片
*/
private Movie movie;
/**
* 租用的天數
*/
private int daysRented;
public Rental() {
}
public Rental(Movie movie, int daysRented) {
this.movie = movie;
this.daysRented = daysRented;
}
public Movie getMovie() {
return movie;
}
public void setMovie(Movie movie) {
this.movie = movie;
}
public int getDaysRented() {
return daysRented;
}
public void setDaysRented(int daysRented) {
this.daysRented = daysRented;
}
}
驗證功能:
public class AppMain {
public static void main(String[] args) {
Customer c = new Customer("admin");
Movie m1 = new Movie("功夫",Movie.REGULAR);
Movie m2 = new Movie("功夫熊貓",Movie.CHILDRENS);
Movie m3 = new Movie("功夫之王",Movie.NEW_RELEASE);
Rental r1 = new Rental(m1,4);
Rental r2 = new Rental(m2,2);
Rental r3 = new Rental(m3,5);
c.addRental(r1);
c.addRental(r2);
c.addRental(r3);
String statement = c.statement();
System.out.println(statement);
}
}
租用的顧客名字為: admin
=========================================
影片名稱:功夫 影片類型:普通片
租期:4天 費用:5.0元
----------------------------------------------------
影片名稱:功夫熊貓 影片類型:兒童片
租期:2天 費用:1.5元
----------------------------------------------------
影片名稱:功夫之王 影片類型:新片
租期:5天 費用:15.0元
----------------------------------------------------
>>>>>>>>>>>>>>>>>>總費用:21.5元<<<<<<<<<<<<<<<<<<<<
>>>>>>>>>>>>>>>>>>本次積分:4<<<<<<<<<<<<<<<<<<<<
2、簡單分析此功能代碼
上面的這一些代碼實現了需求功能,但是你有沒有覺得那個地方看起來很不舒服。【 代碼重構還是需要有一定的編寫代碼的經驗和編寫過一定的代碼】如果看起來沒有任何感覺的話,那說明還需要在多多歷練,多寫一些代碼多思考。
其實對於這個簡單的需求來說,上面的程序已經完全可以正常的工作,但是有沒有覺得 Customer
中 statement
方法做的事情太多了,很多事情原本應該有其他類完成的,卻都放到statement
中完成了。並且當有新需求加入的時候,修改代碼也是很麻煩的,而且容易出錯。
(1)、如果我們打印詳單 要換一種輸出的風格?對於上面的代碼要怎麽進行新功能的添加?
- 對於這個問題,我們可以快速的處理方式為: 拷貝一份
statement
進行修改就ok了。
(2)、在有多種詳單打印輸出風格的前提下, 如果我們影片的計費方式發生變化,又或者是積分計算方式方式變化,又會如何?
- 此時我們需要將所有
statement
方法同時進行修改,並確保各處修改的一致性。【這樣的代碼後期維護起來成本很高,並且容易出錯】
對於上面的一些需求,我們都可以根據已有的代碼【拷貝粘貼】進行功能的快速開發完成。但是隨著需求的越來越復雜,在statement 中 能夠用於 適當修改的點越來越難找,不犯錯的機會也越來越少 。
》》》【書籍小結】《《《
如果你發現自己需要為程序添加一個特性,而代碼結構使你無法很方便的達成目標,那就先重構那個程序,使得特性的添加比較容易進行,然後再添加特性。
》》》【我的思考小結】《《《
對於很多人,加入一家公司或者加入一個項目,並不是所有的功能代碼都是新開發的,很大一部分時間在維護之前開發好的代碼/功能,因為開發者的水平或者需求的變更,導致在原有代碼上在開發新的需求變得異常的復雜和艱難,此時就一定要考慮要重構掉這一塊的代碼了,不要想著繞過,勇敢面對。重構,真的是可以鍛煉自己思維和代碼的編寫能力。
二、重構的第一步
【書籍總結】
重構的第一步永遠相同: 那就是為即將修改的代碼建立一組可靠的測試環境。
這樣做的目的是防止在重構的過程引入新的bug, 好的測試是重構的根本,花時間建立一個優良的測試機制完全值得。測試機制是要讓程序能夠自我檢驗,否則在重構後花大把時間進行比對,這樣會降低開發速度。【程序員在修改bug的過程中又可能在創建新的bug】
三、分解並重組 statement()
當一個方法的長度長的離譜,看到這樣的函數,就應該想著能否進行拆解。代碼塊越小,代碼的功能越容易管理。也方便後面的維護!
代碼重構目標:希望將長長的函數切開,把較小的塊移動到更合適的類中,最終能夠降低代碼重復和擴展。
上面的statement()
方法中,switch看起來就是一個明顯的邏輯泥團。把它提煉到單獨的函數中似乎比較好。
然後在分析函數內的局部變量和參數,其中statement()
while循環中有兩個: thisAmount
、daysRented
和type
, thisAmount
會被修改,後面兩個不會被修改。任何不會被修改的變量都可以被當成參數傳入新的函數,至於被修改的變量就要格外小心。
### 1、第一次重構—方法抽離
第一次修改的代碼:
public String statement() {
// 省略
while (erts.hasMoreElements()) {
// 每個影片的費用
double thisAmount = 0;
Rental rental = (Rental) erts.nextElement();
int daysRented = rental.getDaysRented();
int type = rental.getMovie().getType();
thisAmount = amountFor(type,daysRented);
}
// 省略
return result;
}
/**
* 計算影片租費
* @param type 影片類型
* @param daysRented 租期
* @return
*/
private double amountFor(int type,int daysRented){
double thisAmount = 0;
switch (type) {
case Movie.REGULAR:
thisAmount += 2;
if (daysRented > 2) {
thisAmount += (daysRented - 2) * 1.5;
}
break;
case Movie.NEW_RELEASE:
thisAmount += daysRented * 3;
break;
case Movie.CHILDRENS:
thisAmount += 1.5;
if (daysRented > 3) {
thisAmount += (daysRented - 3) * 1.5;
}
break;
}
return thisAmount;
}
看起來怎麽改了這麽一點點東西啊! 需要註意的是 : 重構步驟的本質,每次修改的幅度都要很小,這樣任何錯誤都很容易發現。【說白了就是步子要邁的小一點,否則容易扯著蛋】
》》》【書籍小結】《《《
重構技術就是以 微小 的步伐修改程序,如果你犯下錯誤,很容易發現它。
》》》【我的思考小結】《《《
就目前而言,上面的這個重構,相比大家在正在編寫代碼的時候也會操作,現在的工具IDEA,對於重構的支持也很好,使用好就是提供效率和提升代碼質量。
2、第二次重構—變量名的修改
這次相對簡單,是一種思想的轉變,上面的 type
局部變量,在命名上相對比較好了,但是還是能夠進一步優化! 可以改為 movieType
! 具體的修改代碼不粘貼了。
更改變量名稱 是值得的行為嗎? 作者說,絕對值得,我認為也絕對值得。要不你寫的變量是 a 、b、c 或者 i、j、k,這樣的代碼真的好嗎? 好的代碼應該清楚的表達出自己的功能,變量名稱是代碼清晰的關鍵【代碼要表現自己的目的】。
》》》【書籍小結】《《《
任何一個傻瓜都能寫出計算機可以理解的代碼,唯有寫出人類容易理解的代碼,才是真正優秀的程序員。
》》》【我的思考小結】《《《
我遇到過一些同事吐槽說目前接受的這個項目的代碼寫的就是一坨,註釋沒有,變量定義也不知是做什麽!然而 當他去開發一個功能的時候,他寫的代碼也是 一坨xiang! 當你看到項目中之前別人代碼寫的不好的,可以抱怨代碼,指責之前開發這個項目的人,但是請不要繼續學習/效仿他們,繼續寫那種像一坨xiang的代碼。
3、第三次重構—代碼搬家
第一次重構的時候抽出一個方法 amountFor
,但是amountFor
是用來計算 影片租期的費用的,似乎 此方法放到Customer
類中有些不妥。 絕大多數情況下,函數應該放到它所使用的數據的所屬對象內。這樣我們就應該將amountFor
放入到 Rental
類中。
如下圖所示:
但是“搬家” 之後應該去掉參數。並且在“搬家”後 修改函數名稱!具體的修改代碼如下:
public class Rental {
/**
* 計算 影片租費
* @return 租費金額
*/
public double getCharge(){
double resultAmount = 0;
int rentalDays = getDaysRented();
switch (getMovie().getType()) {
case Movie.REGULAR:
resultAmount += 2;
if (rentalDays > 2) {
resultAmount += (rentalDays - 2) * 1.5;
}
break;
case Movie.NEW_RELEASE:
resultAmount += rentalDays * 3;
break;
case Movie.CHILDRENS:
resultAmount += 1.5;
if (rentalDays > 3) {
resultAmount += (rentalDays - 3) * 1.5;
}
break;
}
return resultAmount;
}
}
// 其他代碼省略...
public class Customer {
public String statement() {
while (erts.hasMoreElements()) {
thisAmount = amountFor(rental);
}
}
/**
* 計算影片租費
* @param rental
* @return
*/
private double amountFor(Rental rental){
return rental.getCharge();
}
}
改完代碼後,可以使用運行 AppMain
進行驗證! 改完後需要驗證通過!
提煉 積分計算代碼
由於有了上面上面的基礎,積分提煉這一塊的代碼,我們也單獨抽離成一個函數,並且將函數進行搬家到Rental
類中。
修改前後對比圖:
修改後的代碼如下:
public class Rental {
//其他代碼省略
/**
*計算積分
*/
public int getIntegralPoints(){
if (getMovie().getType() == Movie.NEW_RELEASE && getDaysRented() > 1) {
return 2;
}
return 1;
}
}
public class Customer {
public String statement() {
//其他省略...
integralNum += rental.getIntegralPoints();
//其他省略...
}
}
還是再次強調一下:重構的時候最好小步前進, 做一次搬移,在編譯、再測試。這樣才能使出錯的幾率最小。
4、第四次重構—去除臨時變量
在 statement()
中還有臨時變量,有時候,臨時變量確實能讓程序的開發變的簡單和快速!但是 臨時變量也可能是個問題,它們只有在自己的函數中才有效,如果臨時變量太多,導致函數冗長復雜。
下面就將計算 總費用 totalAmount
和 積分 integralNum
臨時變量 進行去除操作。
public class Customer {
public String statement() {
//省略...
while (erts.hasMoreElements()) {
Rental rental = (Rental) erts.nextElement();
int movieType = rental.getMovie().getType();
//省略...
}
result += ">>>>>>>>>>>>>>>>>>總費用:" + getTotalCharge() + "元<<<<<<<<<<<<<<<<<<<< \n";
result += ">>>>>>>>>>>>>>>>>>本次積分:" + getIntegralNum() + "<<<<<<<<<<<<<<<<<<<<";
return result;
}
/**
* 計算影片租費
* @return
*/
private double getTotalCharge(){
double result = 0;
Enumeration erts = rentals.elements();
while (erts.hasMoreElements()) {
Rental rental = (Rental) erts.nextElement();
result += rental.getCharge();
}
return result;
}
/**
* 計算積分
* @return
*/
private double getIntegralNum(){
double result = 0;
Enumeration erts = rentals.elements();
while (erts.hasMoreElements()) {
Rental rental = (Rental) erts.nextElement();
result += rental.getIntegralPoints();
}
return result;
}
}
看了這次的重構,會發現一下問題,那就是性能。原本只需要執行一次的while循環,現在需要執行三次了。 如果while 循環耗時很多,那就可能大大降低程序的性能。 但請註意 前面這句話的加粗詞,只是如果 、可能。 除非進行評測,否則無法確定循環 的執行時間,也無法知道這個循環是否被經常使用以至於影響系統的整體性能。 重構的時候不必擔心這些,優化時你才需要擔心他們。到優化的時候你已處於一個比較有利的位置,可以有更多選擇完成有效優化。
5、 實現用另一種方式打印詳單
通過上面的代碼重構,現在用想使用另一種方式打印租用詳單,整個代碼就簡單很多了。此時 脫下“重構”的帽子,帶上“添加功能”的帽子。實現一個打印htmlStatement()
的方法。
public String htmlStatement() {
Enumeration erts = rentals.elements();
String result = "<title>租用的顧客名字為: " + getName() + "</title>\n";
result += "<p>================================</p>\n";
while (erts.hasMoreElements()) {
// 每個影片的費用
Rental rental = (Rental) erts.nextElement();
int movieType = rental.getMovie().getType();
//打印租用影片詳情
result += "影片名稱:" + rental.getMovie().getTitle() + "\t 影片類型:" + rental.getMovie().getMovieTypeName(movieType)
+ "\n";
result += "租期:" + rental.getDaysRented() + "天\t 費用:" + rental.getCharge() + "元\n";
result += "<p>---------------------------</p>\n";
}
result += "<span>總費用:" + getTotalCharge() + "元<span>\n";
result += "<span>本次積分:" + getIntegralNum() + "<span>";
return result;
}
<title>租用的顧客名字為: admin</title>
<p>================================</p>
影片名稱:功夫 影片類型:普通片
租期:4天 費用:5.0元
<p>---------------------------</p>
影片名稱:功夫熊貓 影片類型:兒童片
租期:2天 費用:1.5元
<p>---------------------------</p>
影片名稱:功夫之王 影片類型:新片
租期:5天 費用:15.0元
<p>---------------------------</p>
<span>總費用:21.5元<span>
<span>本次積分:4.0<span>
通過計算邏輯的提煉,可以很快完成一個htmlStatement()
甚至更多的打印詳單的方式。 並且最要的是如果租費或者積分的計算發生任何變化,我只需要在一個地方進行修改就可以了,而不需要在每一個打印詳單的方法中進行修改然後復制粘貼到其他方法中。 這樣的簡單重構就發生在你我每天開發的項目之中,相信我,多花費的這些時間絕對是值得的。
6、第五次重構-運用多態取代與價格相關的條件邏輯
在實際的開發過程中,用戶的需求一直不斷,在上面目前的重構的代碼中,他們準備修改影片分類規則。我們尚不清楚他們想怎麽做,但似乎新分類法很快就要引入,影片分類發生變化,則費用計算和積分計算也可能會發生變化。所以必須進入費用計算和積分計算中,把因條件而已的代碼(switch語句內case的字句)替換掉,讓我們重新開始重構之路。
看一下之前我們重構之後,計算租費的方法:
public class Rental {
/**
* 影片
*/
private Movie movie;
// 省略...
/**
* 計算 影片租費
* @return 租費金額
*/
public double getCharge(){
double resultAmount = 0;
int rentalDays = getDaysRented();
switch (getMovie().getType()) {
case Movie.REGULAR:
resultAmount += 2;
if (rentalDays > 2) {
resultAmount += (rentalDays - 2) * 1.5;
}
break;
case Movie.NEW_RELEASE:
resultAmount += rentalDays * 3;
break;
case Movie.CHILDRENS:
resultAmount += 1.5;
if (rentalDays > 3) {
resultAmount += (rentalDays - 3) * 1.5;
}
break;
}
return resultAmount;
}
}
使用switch 語句最好是在對象自己的數據上使用,而不是在別人的數據上使用,此時getCharge
應該移動到Movie
類中。 移過去後,我們需要將租期作為參數傳遞進去,因為租期長度來自Rental
對象。
思考:為什麽是選擇將租期長度傳給Movie對象,而不是將影片類型傳遞給Rental對象呢?
因為在本系統中可能發生的變化是加入新的影片類型,這種變化帶有不穩定傾向,如果影片類型有變化,我們希望盡量控制它造成的影響,所以選擇在Movie對象中計算費用。
積分的計算和租費思考類似,修改後代碼:
public class Rental {
/**
* 影片
*/
private Movie movie;
/**
* 租用的天數
*/
private int daysRented;
// 省略...
/**
* 計算 影片租費
* @return 租費金額
*/
public double getCharge(){
return movie.getCharge(daysRented);
}
/**
*計算積分
*/
public int getIntegralPoints(){
return movie.getIntegralPoints(daysRented);
}
}
public class Movie {
/**
* 計算 影片租費
* @return 租費金額
*/
public double getCharge(int rentalDays){
double resultAmount = 0;
switch (getType()) {
case Movie.REGULAR:
resultAmount += 2;
if (rentalDays > 2) {
resultAmount += (rentalDays - 2) * 1.5;
}
break;
case Movie.NEW_RELEASE:
resultAmount += rentalDays * 3;
break;
case Movie.CHILDRENS:
resultAmount += 1.5;
if (rentalDays > 3) {
resultAmount += (rentalDays - 3) * 1.5;
}
break;
}
return resultAmount;
}
/**
*計算積分
*/
public int getIntegralPoints(int rentalDays){
if (getType() == Movie.NEW_RELEASE && rentalDays > 1) {
return 2;
}
return 1;
}
}
修改完後,一定記得測試! 測試通過在繼續開幹。
如果我們有數種影片種類,不同的影片有自己計費法。
Movie (科幻片、愛情片、島國動作片.....),這麽一來,每一種類的片子有自己的一套計費方法,那麽 此時我們可以使用策略模式。(積分也是一樣的)
此段稍微和書中有不同,如想看到詳細細節請看書《重構 改善既有代碼的設計》。
整個修改:
新增
- Price
public abstract class Price {
abstract int getMovieType();
abstract double getCharge(int daysRental);
/**
* 如果是大型項目積分的計算建議還是不要和租費放一起
* 這裏因為是演示demo就放一起了
* @return
*/
abstract int getIntegralPoints(int daysRental);
}
- ChildrensMoviePrice
public class ChildrensMoviePrice extends Price {
@Override
int getMovieType() {
return Movie.CHILDRENS;
}
@Override
double getCharge(int daysRental) {
double resultAmount = 0;
resultAmount += 1.5;
if (daysRental > 3) {
resultAmount += (daysRental - 3) * 1.5;
}
return resultAmount;
}
@Override
int getIntegralPoints(int daysRental) {
return 1;
}
}
- NewReleaseMoviePrice
public class NewReleaseMoviePrice extends Price {
@Override
int getMovieType() {
return Movie.NEW_RELEASE;
}
@Override
double getCharge(int daysRental) {
return daysRental * 3;
}
@Override
int getIntegralPoints(int daysRental) {
if (getMovieType() == Movie.NEW_RELEASE && daysRental > 1) {
return 2;
}
return 1;
}
}
- RegularMoviePrice
public class RegularMoviePrice extends Price {
@Override
int getMovieType() {
return Movie.REGULAR;
}
@Override
double getCharge(int daysRental) {
double resultAmount = 0;
resultAmount += 2;
if (daysRental > 2) {
resultAmount += (daysRental - 2) * 1.5;
}
return resultAmount;
}
@Override
int getIntegralPoints(int daysRental) {
return 1;
}
}
修改
public class Movie {
// 省略其他....
public Movie(String title, Integer type) {
this.title = title;
setType(type);
}
public Integer getType() {
return price.getMovieType();
}
public void setType(Integer type) {
switch (type) {
case REGULAR:
price = new RegularMoviePrice();
break;
case NEW_RELEASE:
price = new NewReleaseMoviePrice();
break;
case CHILDRENS:
price = new ChildrensMoviePrice();
break;
}
}
/**
* 計算 影片租費
* @return 租費金額
*/
public double getCharge(int rentalDays){
return price.getCharge(rentalDays);
}
/**
*計算積分
*/
public int getIntegralPoints(int rentalDays){
return price.getIntegralPoints(rentalDays);
}
}
詳細到這裏,如果你是一步步按照步驟操作,那麽最後重構完的代碼你要有了,我這裏就不在貼一次了。再次強調一下,還是測試。
四、總結
通過一個簡單的例子,你有沒有對“重構改怎麽做”有那麽一點點感覺,重構的整個過程會有很多的技巧,重構行為使得代碼的責任分配更合理,代碼的維護更輕松。希望我們在編寫代碼的開始就有這種思維,有好的代碼風格,能夠寫出好的代碼。
重構的節奏: 測試,小修改、測試、小修改.....,以這種節奏重構,能夠快速安全的前進。
》》》【我的思考小結】《《《
看了第一章,我覺得這本書值得好好品讀,作為開發人員,我們或多或少會在自己項目的開發中留下一些技術債,這裏面就有 為了方便 復制重復的方法,但是一遇到需要修改,很多地方都要進行修改。當初是快了,後面卻慢了! 快到最後變成了慢,最終導致要來還這個債務。 希望看文章的所有夥伴,可以提前就有這種思維方式,那就可以避免後期的很多問題了。
PS:排除 一些 我就是寫代碼的,後期的維護和我沒有關系,也不願自己代碼能越寫越好的人。 因為我就曾遇到這樣的人,他的想法就是我開發完這個功能,說不定我那天走了,反正後面也不是我維護。 對於這類型的人,我們不能改變他們,但是我希望如果你遇到了,請不要學他。不管我們在這家公司呆多久,對自己的代碼負責,對自己的人生負責。
如果您覺得這篇博文對你有幫助,請點贊或者喜歡,讓更多的人看到,謝謝!
如果帥氣(美麗)、睿智(聰穎),和我一樣簡單善良的你看到本篇博文中存在問題,請指出,我虛心接受你讓我成長的批評,謝謝閱讀!
祝你今天開心愉快!
歡迎訪問我的csdn博客和關註的個人微信公眾號!
願你我在人生的路上能都變成最好的自己,能夠成為一個獨擋一面的人。
不管做什麽,只要堅持下去就會看到不一樣!在路上,不卑不亢!
博客首頁 : http://blog.csdn.net/u010648555
? 阿飛(dufyun)
【《重構 改善既有代碼的設計》學習筆記1】重構:第一個案例