《重构》 — Java示例:影片出租店程序(3、重构——分解并重组Statement)

示例:影片出租店程序(重构——分解并重组Statement)
步骤:
1、提炼“金额计算”代码
1.1、提炼“逻辑泥团”——“提炼方法(Extract Method)”
Statement()中一个明显的“逻辑泥团”就是switch语句,把它提炼到独立函数“AmountFor”中。

Eclipse工具重构步骤:
(1)、选中代码
 
(2)、右键选择“Extract Method”
 
(3)、输入方法名“amountFor”
 
(4)、调整代码如下
    public String statement() {
        double totalAmount = 0; //--总消费金额
        int frequentRenterPoints = 0; //--常客积点
        Enumeration rentals = _rentals.elements();
        String result = "Rental Record for " + get_name() + "/n";
        
        while(rentals.hasMoreElements()){
            double thisAmount = 0;
            Rental each = (Rental) rentals.nextElement(); //--取得一笔租借记录
            
            thisAmount = amountFor(each);
            
            //---累加常客积点
            frequentRenterPoints ++;
            if (each.get_movie().get_priceCode() == Movie.NEW_RELEASE &&
                each.get_daysRented() > 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;
    }

    private double amountFor(Rental each) {     //--计算一笔租片费用
        double thisAmount = 0;
        
        switch(each.get_movie().get_priceCode()){  //--取得影片出租价格
            case Movie.REGULAR: //--普通片
                thisAmount += 2;
                if (each.get_daysRented() > 2)
                    thisAmount += (each.get_daysRented() - 2) * 1.5;
                break;
            case Movie.NEW_RELEASE:  //--新片
                thisAmount += each.get_daysRented() * 3;
                break;
            case Movie.CHILDRENS: //--儿童片
                thisAmount += 1.5;
                if (each.get_daysRented() > 3)
                    thisAmount += (each.get_daysRented() - 3) * 1.5;
                break;
        }
        
        return thisAmount;
    }

1.2、规范变量名称
好的代码应该清楚表达出自己的功能,变量名称是代码清浙的关键。

Eclipse工具重构步骤:
(1)、选中“变量名称”
(2)、右键选中“Rename”
 
(3)、输入新的“变量名称”
 
(4)、调整代码如下
    private double amountFor(Rental aRental) {    //--计算一笔租片费用
        double result = 0;
        
        switch(aRental.get_movie().get_priceCode()){  //--取得影片出租价格
            case Movie.REGULAR: //--普通片
                result += 2;
                if (aRental.get_daysRented() > 2)
                    result += (aRental.get_daysRented() - 2) * 1.5;
                break;
            case Movie.NEW_RELEASE:  //--新片
                result += aRental.get_daysRented() * 3;
                break;
            case Movie.CHILDRENS: //--儿童片
                result += 1.5;
                if (aRental.get_daysRented() > 3)
                    result += (aRental.get_daysRented() - 3) * 1.5;
                break;
        }
        
        return result;
    }

1.3、搬移“金额计算”代码——“搬移方法(Move Method)”
AmountFor函数使用了来自Rental类的信息.却没有使用来自Customer类的信息。这表明它可能是被放错了位置,应将AmountFor()移到Rental类中。此外,还要在搬移的同时变更函数名称(AmountFor –> GetCharge)。

Eclipse工具重构步骤:
1、搬移方法
(1)、选中“方法名称”
(2)、右键选择“Move”
 
(3)、搬移方法
 
(4)、调整代码如下
package Movie_Ref;

/**
* 租赁
*
*/
public class Rental {
    private Movie _movie; //影片
    private int _daysRented; //租期
    
    public Rental(Movie movie, int daysRented) {
        _movie = movie;
        _daysRented = daysRented;
    }

    public int get_daysRented() {
        return _daysRented;
    }

    public Movie get_movie() {
        return _movie;
    }

    double amountFor() {    //--计算一笔租片费用
        double result = 0;
        
        switch(get_movie().get_priceCode()){  //--取得影片出租价格
            case Movie.REGULAR: //--普通片
                result += 2;
                if (get_daysRented() > 2)
                    result += (get_daysRented() - 2) * 1.5;
                break;
            case Movie.NEW_RELEASE:  //--新片
                result += get_daysRented() * 3;
                break;
            case Movie.CHILDRENS: //--儿童片
                result += 1.5;
                if (get_daysRented() > 3)
                    result += (get_daysRented() - 3) * 1.5;
                break;
        }
        
        return result;
    }
}

2、变更函数名称
(1)、选中“函数名称”
(2)、右键选中“Rename”
 
(3)、输入新的“函数名称”
 
(4)、调整代码如下
    double getCharge() {    //--计算一笔租片费用
        double result = 0;
        
        switch(get_movie().get_priceCode()){  //--取得影片出租价格
            case Movie.REGULAR: //--普通片
                result += 2;
                if (get_daysRented() > 2)
                    result += (get_daysRented() - 2) * 1.5;
                break;
            case Movie.NEW_RELEASE:  //--新片
                result += get_daysRented() * 3;
                break;
            case Movie.CHILDRENS: //--儿童片
                result += 1.5;
                if (get_daysRented() > 3)
                    result += (get_daysRented() - 3) * 1.5;
                break;
        }
        
        return result;
    }


1.4、删除临时变量——“替换临时变量(Replace Temp with Query)”
Statement方法中的临时变量thisAmount接受each.GetCharge的执行结果,然后就不再有任何改变。所以尽量除去这一类临时变量。

    public String statement() {
        double totalAmount = 0; //--总消费金额
        int frequentRenterPoints = 0; //--常客积点
        Enumeration rentals = _rentals.elements();
        String result = "Rental Record for " + get_name() + "/n";
        
        while(rentals.hasMoreElements()){
            Rental each = (Rental) rentals.nextElement(); //--取得一笔租借记录
                        
            //---累加常客积点
            frequentRenterPoints ++;
            if (each.get_movie().get_priceCode() == Movie.NEW_RELEASE &&
                each.get_daysRented() > 1)
                frequentRenterPoints ++;
            //---显示此笔租借数据
            result += "/t" + each.get_movie().get_title() + "/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;
    }

2、提炼“常客积点计算”代码
2.1、提炼“常客积点计算”代码——“提炼函数(Extract Method)”
首先,我们需要针对“常客积点计算”这部分代码运用“提炼函数(Extract Method)”重构准则,将其提炼为函数“GetFrequentRenterPoints”。
    public String statement() {
        double totalAmount = 0; //--总消费金额
        int frequentRenterPoints = 0; //--常客积点
        Enumeration rentals = _rentals.elements();
        String result = "Rental Record for " + get_name() + "/n";
        
        while(rentals.hasMoreElements()){
            Rental each = (Rental) rentals.nextElement(); //--取得一笔租借记录
                        
            //---累加常客积点
            frequentRenterPoints = getFrequentRenterPoints(frequentRenterPoints, each);
            //---显示此笔租借数据
            result += "/t" + each.get_movie().get_title() + "/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;
    }

    private int getFrequentRenterPoints(int frequentRenterPoints, Rental each) {
        frequentRenterPoints ++;
        if (each.get_movie().get_priceCode() == Movie.NEW_RELEASE &&
            each.get_daysRented() > 1)
            frequentRenterPoints ++;
        
        return frequentRenterPoints;
    }

然后,在“GetFrequentRenterPoints”函数内找到局部变量each,它可以被当作参数传入新函数中。另一个临时变量是frequentRenterPoints。本例中的它在被使用之前已经先有初值,但提炼出来的函数并没有读取该值,所以我们不需要将它当作参数传进去,只需对它执行“追加赋值操作“就行 。

    public String statement() {
        double totalAmount = 0; //--总消费金额
        int frequentRenterPoints = 0; //--常客积点
        Enumeration rentals = _rentals.elements();
        String result = "Rental Record for " + get_name() + "/n";
        
        while(rentals.hasMoreElements()){
            Rental each = (Rental) rentals.nextElement(); //--取得一笔租借记录
                        
            //---累加常客积点
            frequentRenterPoints += getFrequentRenterPoints(each);
            //---显示此笔租借数据
            result += "/t" + each.get_movie().get_title() + "/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;
    }

    private int getFrequentRenterPoints(Rental each) {
        int frequentRenterPoints = 0; //--常客积点
                
        if (each.get_movie().get_priceCode() == Movie.NEW_RELEASE &&
            each.get_daysRented() > 1)
            frequentRenterPoints = 2;
        else
            frequentRenterPoints = 1;
        
        return frequentRenterPoints;
    }

2.2、规范变量名称
    private int getFrequentRenterPoints(Rental each) {
        if (each.get_movie().get_priceCode() == Movie.NEW_RELEASE &&
            each.get_daysRented() > 1)
            return 2;
        else
            return 1;
    }


2.3、搬移“常客积点计算”代码——“搬移方法(Move Method)”
public class Customer {
    …………
    
    public String statement() {
        double totalAmount = 0; //--总消费金额
        int frequentRenterPoints = 0; //--常客积点
        Enumeration rentals = _rentals.elements();
        String result = "Rental Record for " + get_name() + "/n";
        
        while(rentals.hasMoreElements()){
            Rental each = (Rental) rentals.nextElement(); //--取得一笔租借记录
                        
            //---累加常客积点
            frequentRenterPoints += each.getFrequentRenterPoints();
            //---显示此笔租借数据
            result += "/t" + each.get_movie().get_title() + "/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;
    }
}

public class Rental {
    …………

    int getFrequentRenterPoints() {
        if (get_movie().get_priceCode() == Movie.NEW_RELEASE &&
            get_daysRented() > 1)
            return 2;
        else
            return 1;
    }
}

3、总量计算(去除临时变量)
statement函数中有两个临时变量totalAmount和frequentRenterPoints,两者都是用来从Customer对象相关的Rental对象中获得某个总量。因为不论 ASCll 版或 HTML 版都需要这些总量。所以,我们运用“替换临时变量(Replace Temp with Query)”和“查询方法(Query Method)”来取代临时变量。
通过去除临时变量,可以将冗长复杂的函数中的逻辑理顺,并使其更为清晰。如果系统中的其它地方需要这些信息,也可以很轻松地将“查询方法”加入Customer类的公共接口。 

3.1、总消费金额
首先,使用“查询方法GetTotalCharge”去除“临时变量totalAmount”。

    private double getTotalCharge() {
        double totalAmount = 0; //--总消费金额        
        Enumeration rentals = _rentals.elements();
        
        while(rentals.hasMoreElements()){
            Rental each = (Rental) rentals.nextElement(); //--取得一笔租借记录
            totalAmount += each.getCharge();
        }        
        
        return totalAmount;
    }

然后规范getTotalCharge方法中变量名称。

public class Customer {
    …………
    
    public String statement() {        
        int frequentRenterPoints = 0; //--常客积点
        Enumeration rentals = _rentals.elements();
        String result = "Rental Record for " + get_name() + "/n";
        
        while(rentals.hasMoreElements()){
            Rental each = (Rental) rentals.nextElement(); //--取得一笔租借记录
                        
            //---累加常客积点
            frequentRenterPoints += each.getFrequentRenterPoints();
            //---显示此笔租借数据
            result += "/t" + each.get_movie().get_title() + "/t" + 
                      String.valueOf(each.getCharge()) + "/n";
        }
        //---结尾打印
        result += "Amount owed is " + String.valueOf(getTotalCharge()) + "/n";
        result += "You earned " + String.valueOf(frequentRenterPoints) +
                 " frequent renter points";
        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;
    }
}

3.2、总常客积点
首先,用“查询方法GetTotalFrequentRenterPoints”去除“临时变量frequentRenterPoints”。

    private int getTotalFrequentRenterPoints() {
        int frequentRenterPoints = 0; //--常客积点        
        Enumeration rentals = _rentals.elements();
        
        while(rentals.hasMoreElements()){
            Rental each = (Rental) rentals.nextElement(); //--取得一笔租借记录        
            //---累加常客积点
            frequentRenterPoints += each.getFrequentRenterPoints();
        }
        
        return frequentRenterPoints;
    }

然后规范GetTotalFrequentRenterPoints方法中变量名称。

public class Customer {
    …………
    
    public String statement() {        
        Enumeration rentals = _rentals.elements();
        String result = "Rental Record for " + get_name() + "/n";
        
        while(rentals.hasMoreElements()){
            Rental each = (Rental) rentals.nextElement(); //--取得一笔租借记录                        
            //---显示此笔租借数据
            result += "/t" + each.get_movie().get_title() + "/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 result = 0; //--常客积点        
        Enumeration rentals = _rentals.elements();
        
        while(rentals.hasMoreElements()){
            Rental each = (Rental) rentals.nextElement(); //--取得一笔租借记录        
            //---累加常客积点
            result += each.getFrequentRenterPoints();
        }
        
        return result;
    }

    …………
}

代码:
1、Customer.java
package Movie_Ref;

import java.util.Enumeration;
import java.util.Vector;

public class Customer {
    private String _name;//姓名
    private Vector _rentals = new Vector(); //租借记录
    
    public Customer(String name) {
        _name = name;
    }

    public void addRental(Rental obj) {
        _rentals.addElement(obj);
    }

    public String get_name() {
        return _name;
    }
    
    public String statement() {        
        Enumeration rentals = _rentals.elements();
        String result = "Rental Record for " + get_name() + "/n";
        
        while(rentals.hasMoreElements()){
            Rental each = (Rental) rentals.nextElement(); //--取得一笔租借记录                        
            //---显示此笔租借数据
            result += "/t" + each.get_movie().get_title() + "/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 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;
    }    
}

2、Movie.java
package Movie_Ref;

/**
* 影片
*
*/
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) {
        _title = title;
        _priceCode = priceCode;
    }

    public int get_priceCode() {
        return _priceCode;
    }

    public void set_priceCode(int code) {
        _priceCode = code;
    }

    public String get_title() {
        return _title;
    }
    
}

3、Rental.java
package Movie_Ref;

/**
* 租赁
*
*/
public class Rental {
    private Movie _movie; //影片
    private int _daysRented; //租期
    
    public Rental(Movie movie, int daysRented) {
        _movie = movie;
        _daysRented = daysRented;
    }

    public int get_daysRented() {
        return _daysRented;
    }

    public Movie get_movie() {
        return _movie;
    }

    double getCharge() {    //--计算一笔租片费用
        double result = 0;
        
        switch(get_movie().get_priceCode()){  //--取得影片出租价格
            case Movie.REGULAR: //--普通片
                result += 2;
                if (get_daysRented() > 2)
                    result += (get_daysRented() - 2) * 1.5;
                break;
            case Movie.NEW_RELEASE:  //--新片
                result += get_daysRented() * 3;
                break;
            case Movie.CHILDRENS: //--儿童片
                result += 1.5;
                if (get_daysRented() > 3)
                    result += (get_daysRented() - 3) * 1.5;
                break;
        }
        
        return result;
    }

    int getFrequentRenterPoints() {
        if (get_movie().get_priceCode() == Movie.NEW_RELEASE &&
            get_daysRented() > 1)
            return 2;
        else
            return 1;
    }
}

你可能感兴趣的:(代码重构)