1. 程式人生 > >《重構-改善程式碼既有的設計》重構,第一個案例

《重構-改善程式碼既有的設計》重構,第一個案例

起點:編寫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