《重構-改善程式碼既有的設計》重構,第一個案例
起點:編寫3個類的程式碼
1、第一個類-影片(Movie):
package com.lee.test.aFirstExample;
public class Movie {
/**
* @param title
* @param priceCode
*/
public Movie(String title, int priceCode) {
super();
this.title = title;
this.priceCode = priceCode;
}
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 String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getPriceCode() {
return priceCode;
}
public void setPriceCode(int priceCode) {
this.priceCode = priceCode;
}
}
2、第二個類-租賃(Rental):
package com.lee.test.aFirstExample;
public class Rental {
/**
* @param movie
* @param dayRented
*/
public Rental (Movie movie, int dayRented) {
super();
this.movie = movie;
this.dayRented = dayRented;
}
private Movie movie;
public Movie getMovie() {
return movie;
}
private int dayRented;
public int getDayRented() {
return dayRented;
}
}
3、第三個類-消費者(Customer):
package com.lee.test.aFirstExample;
import java.util.Enumeration;
import java.util.Vector;
public class Customer {
/**
* @param name
*/
public Customer(String name) {
super();
this.name = name;
}
private String name;
public String getName() {
return name;
}
private Vector rentals = new Vector();
public void addRental(Rental arg)
{
rentals.addElement(arg);
}
public String statement(){
double totalAmount = 0;
int frequentRenterPoints = 0;
Enumeration rentalss = rentals.elements();
String result = "Rental Record for" +" "+ getName()+"\n";
while(rentalss.hasMoreElements())
{
double thisAmount = 0;
Rental each = (Rental) rentalss.nextElement();
switch(each.getMovie().getPriceCode())
{
case Movie.regular:
thisAmount += 2;
if(each.getDayRented()>2)
thisAmount += (each.getDayRented()-2)*1.5;
break;
case Movie.new_release:
thisAmount += each.getDayRented()*3;
break;
case Movie.childrens:
thisAmount += 1.5;
if(each.getDayRented()>3)
thisAmount += (each.getDayRented()-3)*1.5;
break;
}
//積分 每借一張加1個積分
frequentRenterPoints++;
//積分累加條件 新版本的片子,借的時間大於1天
if((each.getMovie().getPriceCode()==Movie.new_release)&&each.getDayRented()>1)
{
frequentRenterPoints++;
}
result +="\t" +each.getMovie().getTitle()+"\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;
}
}
4、自己定義客戶端(Client)。
package com.lee.test.aFirstExample;
public class Client {
public static void main(String[] args) {
Movie mov = new Movie("metal",2);
Rental ren = new Rental(mov,8);
Customer cus = new Customer("Lee");
cus.addRental(ren);
System.out.println(cus.statement());
}
}
5、輸出結果
Rental Record for Lee
metal 9.0
Amount owed is 9.0
You earned 1 frequent renter points
6、重構第一步
提煉方法(Extract Method)
提煉Switch/Case分支到一個方法,修改後Customer程式碼:
package com.lee.test.aFirstExample;
import java.util.Enumeration;
import java.util.Vector;
public class Customer {
/**
* @param name
*/
public Customer(String name) {
super();
this.name = name;
}
private String name;
public String getName() {
return name;
}
private Vector rentals = new Vector();
public void addRental(Rental arg)
{
rentals.addElement(arg);
}
public String statement(){
double totalAmount = 0;
int frequentRenterPoints = 0;
Enumeration rentalss = rentals.elements();
String result = "Rental Record for" +" "+ getName()+"\n";
while(rentalss.hasMoreElements())
{
double thisAmount = 0;
Rental each = (Rental) rentalss.nextElement();
thisAmount = amountFor(each); //提煉方法
/*switch(each.getMovie().getPriceCode())
{
case Movie.regular:
thisAmount += 2;
if(each.getDayRented()>2)
thisAmount += (each.getDayRented()-2)*1.5;
break;
case Movie.new_release:
thisAmount += each.getDayRented()*3;
break;
case Movie.childrens:
thisAmount += 1.5;
if(each.getDayRented()>3)
thisAmount += (each.getDayRented()-3)*1.5;
break;
}*/
//積分 每借一張加1個積分
frequentRenterPoints++;
//積分累加條件 新版本的片子,借的時間大於1天
if((each.getMovie().getPriceCode()==Movie.new_release)&&each.getDayRented()>1)
{
frequentRenterPoints++;
}
result +="\t" +each.getMovie().getTitle()+"\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;
}
private double amountFor(Rental each)
{
double thisAmount = 0;
switch(each.getMovie().getPriceCode())
{
case Movie.regular:
thisAmount += 2;
if(each.getDayRented()>2)
thisAmount += (each.getDayRented()-2)*1.5;
break;
case Movie.new_release:
thisAmount += each.getDayRented()*3;
break;
case Movie.childrens:
thisAmount += 1.5;
if(each.getDayRented()>3)
thisAmount += (each.getDayRented()-3)*1.5;
break;
}
return thisAmount;
}
}
備註:《Refactoring—Improving the Design of the Existing Code》書中說SmallTalk編譯器具備重構功能,嘗試使用Myeclipse 2015編譯器使用。
操作過程:
(1) 選中需要重構的程式碼片段;
(2) IDE編譯器的功能導航;
(3) 根據IDE編譯器對話方塊提示;
(4)用Myeclipse 2015編譯器自動重構的程式碼如下,用Client客戶端呼叫結果一致,修改後Customer程式碼:
package com.lee.test.aFirstExample;
import java.util.Enumeration;
import java.util.Vector;
public class Customer {
/**
* @param name
*/
public Customer(String name) {
super();
this.name = name;
}
private String name;
public String getName() {
return name;
}
private Vector rentals = new Vector();
public void addRental(Rental arg)
{
rentals.addElement(arg);
}
public String statement(){
double totalAmount = 0;
int frequentRenterPoints = 0;
Enumeration rentalss = rentals.elements();
String result = "Rental Record for" +" "+ getName()+"\n";
while(rentalss.hasMoreElements())
{
double thisAmount = 0;
Rental each = (Rental) rentalss.nextElement();
// thisAmount = amountFor(each); //提煉方法
thisAmount = amountFor(thisAmount, each);
//積分 每借一張加1個積分
frequentRenterPoints++;
//積分累加條件 新版本的片子,借的時間大於1天
if((each.getMovie().getPriceCode()==Movie.new_release)&&each.getDayRented()>1)
{
frequentRenterPoints++;
}
result +="\t" +each.getMovie().getTitle()+"\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;
}
/**
* @param thisAmount
* @param each
* @return
*/
private double amountFor(double thisAmount, Rental each) {
switch(each.getMovie().getPriceCode())
{
case Movie.regular:
thisAmount += 2;
if(each.getDayRented()>2)
thisAmount += (each.getDayRented()-2)*1.5;
break;
case Movie.new_release:
thisAmount += each.getDayRented()*3;
break;
case Movie.childrens:
thisAmount += 1.5;
if(each.getDayRented()>3)
thisAmount += (each.getDayRented()-3)*1.5;
break;
}
return thisAmount;
}
/*private double amountFor(Rental each)
{
double thisAmount = 0;
switch(each.getMovie().getPriceCode())
{
case Movie.regular:
thisAmount += 2;
if(each.getDayRented()>2)
thisAmount += (each.getDayRented()-2)*1.5;
break;
case Movie.new_release:
thisAmount += each.getDayRented()*3;
break;
case Movie.childrens:
thisAmount += 1.5;
if(each.getDayRented()>3)
thisAmount += (each.getDayRented()-3)*1.5;
break;
}
return thisAmount;
}*/
}
7、重構第二步
移動方法(Move Method)
將amountFor()方法從Customer類中移動到Rental類中,並修改方法名稱為getCharge()。
移動amountFor()方法後的Customer程式碼:
package com.lee.test.aFirstExample;
import java.util.Enumeration;
import java.util.Vector;
public class Customer {
/**
* @param name
*/
public Customer(String name) {
super();
this.name = name;
}
private String name;
public String getName() {
return name;
}
private Vector rentals = new Vector();
public void addRental(Rental arg)
{
rentals.addElement(arg);
}
public String statement(){
double totalAmount = 0;
int frequentRenterPoints = 0;
Enumeration rentalss = rentals.elements();
String result = "Rental Record for" +" "+ getName()+"\n";
while(rentalss.hasMoreElements())
{
double thisAmount = 0;
Rental each = (Rental) rentalss.nextElement();
// thisAmount = amountFor(each); //提煉方法
thisAmount = each.getCharge(thisAmount);
//積分 每借一張加1個積分
frequentRenterPoints++;
//積分累加條件 新版本的片子,借的時間大於1天
if((each.getMovie().getPriceCode()==Movie.new_release)&&each.getDayRented()>1)
{
frequentRenterPoints++;
}
result +="\t" +each.getMovie().getTitle()+"\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;
}
/*private double amountFor(Rental each)
{
double thisAmount = 0;
switch(each.getMovie().getPriceCode())
{
case Movie.regular:
thisAmount += 2;
if(each.getDayRented()>2)
thisAmount += (each.getDayRented()-2)*1.5;
break;
case Movie.new_release:
thisAmount += each.getDayRented()*3;
break;
case Movie.childrens:
thisAmount += 1.5;
if(each.getDayRented()>3)
thisAmount += (each.getDayRented()-3)*1.5;
break;
}
return thisAmount;
}*/
}
移動amountFor()方法後的Rental程式碼:
package com.lee.test.aFirstExample;
public class Rental {
/**
* @param movie
* @param dayRented
*/
public Rental(Movie movie, int dayRented) {
super();
this.movie = movie;
this.dayRented = dayRented;
}
private Movie movie;
public Movie getMovie() {
return movie;
}
private int dayRented;
public int getDayRented() {
return dayRented;
}
/**
* @param thisAmount
* @return
*/
double getCharge(double thisAmount) {
switch(getMovie().getPriceCode())
{
case Movie.regular:
thisAmount += 2;
if(getDayRented()>2)
thisAmount += (getDayRented()-2)*1.5;
break;
case Movie.new_release:
thisAmount += getDayRented()*3;
break;
case Movie.childrens:
thisAmount += 1.5;
if(getDayRented()>3)
thisAmount += (getDayRented()-3)*1.5;
break;
}
return thisAmount;
}
}
8、重構第三步
以查詢取代臨時變數(Replace temp with query)
除去臨時變數thisAmount;
修改後的statement賬單方法:
public String statement(){
double totalAmount = 0;
int frequentRenterPoints = 0;
Enumeration rentalss = rentals.elements();
String result = "Rental Record for" +" "+ getName()+"\n";
while(rentalss.hasMoreElements())
{
Rental each = (Rental) rentalss.nextElement();
// thisAmount = amountFor(each); //提煉方法
//積分 每借一張加1個積分
frequentRenterPoints++;
//積分累加條件 新版本的片子,借的時間大於1天
if((each.getMovie().getPriceCode()==Movie.new_release)&&each.getDayRented()>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 earned "+String.valueOf(frequentRenterPoints)+" "
+"frequent renter points";
return result;
}
修改Rental類的getCharge()方法,由傳參改為內部變數:
double getCharge() {
double result = 0;
switch(getMovie().getPriceCode())
{
case Movie.regular:
result += 2;
if(getDayRented()>2)
result += (getDayRented()-2)*1.5;
break;
case Movie.new_release:
result += getDayRented()*3;
break;
case Movie.childrens:
result += 1.5;
if(getDayRented()>3)
result += (getDayRented()-3)*1.5;
break;
}
return result;
}
9、重構第四步:
提取方法提取“常客積分計算”:
int getFrequentRenterPoints() {
//積分累加條件 新版本的片子,借的時間大於1天
if((getMovie().getPriceCode()==Movie.new_release)&&getDayRented()>1)
{
return 2;
}
return 1;
}
10、重構第五步:
移動方法(Move Method)
將getFrequentRenterPoints()方法從Customer類中移動到Rental類中。
11、重構第六步:
以查詢取代臨時變數(Replace temp with query)
除去臨時變數thisAmount;
除去臨時變數frequentRenterPoints;
修改後的Customer類程式碼:
public String statement(){
Enumeration rentalss = rentals.elements();
String result = "Rental Record for" +" "+ getName()+"\n";
while(rentalss.hasMoreElements())
{
Rental each = (Rental) rentalss.nextElement();
result +="\t" +each.getMovie().getTitle()+"\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;
}
private int getTotalFrequentRenterPoints() {
int points = 0;
Enumeration rentalss = rentals.elements();
while(rentalss.hasMoreElements())
{
Rental each = (Rental) rentalss.nextElement();
points += each.getFrequentRenterPoints();
}
return points;
}
private double getTotalCharge()
{
double result = 0;
Enumeration rentalss = rentals.elements();
while(rentalss.hasMoreElements())
{
Rental each = (Rental) rentalss.nextElement();
result += each.getCharge();
}
return result;
}
12、重構第七步:
將影片相關的費用、積分方法Move到Movie類中,將Rental的屬性daysRented作為引數。
修改後的Rental類:
package com.lee.test.aFirstExample;
public class Rental {
/**
* @param movie
* @param dayRented
*/
public Rental(Movie movie, int dayRented) {
super();
this.movie = movie;
this.dayRented = dayRented;
}
private Movie movie;
public Movie getMovie() {
return movie;
}
private int dayRented;
public int getDayRented() {
return dayRented;
}
}
修改後的Movie類:
package com.lee.test.aFirstExample;
public class Movie {
/**
* @param title
* @param priceCode
*/
public Movie(String title, int priceCode) {
super();
this.title = title;
this.priceCode = priceCode;
}
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 String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getPriceCode() {
return priceCode;
}
public void setPriceCode(int priceCode) {
this.priceCode = priceCode;
}
/**
* @param daysRented
* @return
*/
double getCharge(int daysRented) {
double result = 0;
switch(getPriceCode())
{
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;
}
/**
* @param daysRented
* @return
*/
int getFrequentRenterPoints(int daysRented) {
//積分累加條件 新版本的片子,借的時間大於1天
if((getPriceCode()== Movie.new_release) && daysRented>1)
{
return 2;
}
return 1;
}
}
修改後的Customer類:
package com.lee.test.aFirstExample;
import java.util.Enumeration;
import java.util.Vector;
public class Customer {
/**
* @param name
*/
public Customer(String name) {
super();
this.name = name;
}
private String name;
public String getName() {
return name;
}
private Vector rentals = new Vector();
public void addRental(Rental arg)
{
rentals.addElement(arg);
}
public String statement(){
Enumeration rentalss = rentals.elements();
String result = "Rental Record for" +" "+ getName()+"\n";
while(rentalss.hasMoreElements())
{
Rental each = (Rental) rentalss.nextElement();
result +="\t" +each.getMovie().getTitle()+"\t"
+String.valueOf(each.getMovie().getCharge(each.getDayRented()))+"\n";
}
result += "Amount owed is "+ String.valueOf(getTotalCharge())+"\n";
result +="You earned "+String.valueOf(getTotalFrequentRenterPoints())+" "
+"frequent renter points";
return result;
}
private int getTotalFrequentRenterPoints() {
int points = 0;
Enumeration rentalss = rentals.elements();
while(rentalss.hasMoreElements())
{
Rental each = (Rental) rentalss.nextElement();
points += each.getMovie().getFrequentRenterPoints(each.getDayRented());
}
return points;
}
private double getTotalCharge()
{
double result = 0;
Enumeration rentalss = rentals.elements();
while(rentalss.hasMoreElements())
{
Rental each = (Rental) rentalss.nextElement();
result += each.getMovie().getCharge(each.getDayRented());
}
return result;
}
}
13、重構第八步:
運用多型取代與價格相關的條件邏輯。
建立Price價格類,作為抽象類給多種影片型別繼承,關聯到Movie影片類中,Price類:
package com.lee.test.aFirstExample;
public abstract class Price {
abstract int getPriceCode();
abstract double getCharge(int daysRented);
}
分別建立3個不同價格型別的類:
NewReleasePrice類:
package com.lee.test.aFirstExample;
public class NewReleasePrice extends Price{
@Override
int getPriceCode() {
return Movie.new_release;
}
double getCharge(int daysRented)
{
double result = 0;
result += daysRented*3;
return result;
}
}
ChildrensPrice 類:
package com.lee.test.aFirstExample;
public class ChildrensPrice extends Price{
@Override
int getPriceCode() {
return Movie.childrens;
}
double getCharge(int daysRented)
{
double result = 0;
result += 1.5;
if(daysRented>3)
result += (daysRented-3)*1.5;
return result;
}
}
RegularPrice 類:
package com.lee.test.aFirstExample;
public class RegularPrice extends Price{
@Override
int getPriceCode() {
return Movie.regular;
}
double getCharge(int daysRented)
{
double result = 0;
result += 2;
if(daysRented>2)
result += (daysRented-2)*1.5;
return result;
}
}
修改後的Movie類:
package com.lee.test.aFirstExample;
public class Movie {
/**
* @param title
* @param priceCode
*/
public Movie(String title, int priceCode) {
super();
this.title = title;
this.priceCode = priceCode;
}
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;
private Price price;
public Price getPrice() {
return price;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getPriceCode() {
return price.getPriceCode();
}
public void setPriceCode(int arg) {
switch(arg)
{
case regular:
price = new RegularPrice();
break;
case new_release:
price = new NewReleasePrice();
break;
case childrens:
price = new ChildrensPrice();
break;
default:
throw new IllegalArgumentException("Incorrect Price Code");
}
}
/**
* @param daysRented
* @return
*/
int getFrequentRenterPoints(int daysRented) {
//積分累加條件 新版本的片子,借的時間大於1天
if((getPriceCode()== Movie.new_release) && daysRented>1)
{
return 2;
}
return 1;
}
}
Rental類:
package com.lee.test.aFirstExample;
public class Rental {
/**
* @param movie
* @param dayRented
*/
public Rental(Movie movie, int dayRented) {
super();
this.movie = movie;
this.dayRented = dayRented;
}
private Movie movie;
public Movie getMovie() {
return movie;
}
private int dayRented;
public int getDayRented() {
return dayRented;
}
}
Customer類:
package com.lee.test.aFirstExample;
import java.util.Enumeration;
import java.util.Vector;
public class Customer {
/**
* @param name
*/
public Customer(String name) {
super();
this.name = name;
}
private String name;
public String getName() {
return name;
}
private Vector rentals = new Vector();
public void addRental(Rental arg)
{
rentals.addElement(arg);
}
public String statement(){
Enumeration rentalss = rentals.elements();
String result = "Rental Record for" +" "+ getName()+"\n";
while(rentalss.hasMoreElements())
{
Rental each = (Rental) rentalss.nextElement();
result +="\t" +each.getMovie().getTitle()+"\t"
+String.valueOf(each.getMovie().getPrice().getCharge(each.getDayRented()))+"\n";
}
result += "Amount owed is "+ String.valueOf(getTotalCharge())+"\n";
result +="You earned "+String.valueOf(getTotalFrequentRenterPoints())+" "
+"frequent renter points";
return result;
}
private int getTotalFrequentRenterPoints() {
int points = 0;
Enumeration rentalss = rentals.elements();
while(rentalss.hasMoreElements())
{
Rental each = (Rental) rentalss.nextElement();
points += each.getMovie().getFrequentRenterPoints(each.getDayRented());
}
return points;
}
private double getTotalCharge()
{
double result = 0;
Enumeration rentalss = rentals.elements();
while(rentalss.hasMoreElements())
{
Rental each = (Rental) rentalss.nextElement();
result += each.getMovie().getPrice().getCharge(each.getDayRented());
}
return result;
}
}
執行的Client:
package com.lee.test.aFirstExample;
public class Client {
public static void main(String[] args) {
Movie mov1 = new Movie("metal",2);
mov1.setPriceCode(2);
Rental ren = new Rental(mov1,8);
Movie mov2 = new Movie("apple",1);
mov2.setPriceCode(1);
Rental xxx = new Rental(mov2,6);
Customer cus = new Customer("Lee");
cus.addRental(ren);
cus.addRental(xxx);
System.out.println(cus.statement());
}
}
列印輸出結果:
Rental Record for Lee
metal 9.0
apple 18.0
Amount owed is 27.0
You earned 3 frequent renter points