`
381895649
  • 浏览: 227798 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

java 代码重构-第一章(去除临时变量)

    博客分类:
  • java
阅读更多

上一篇文章:java 代码重构-第一章(提炼代码)
说到了代码的提炼,把一些代码提炼出来写成一个方法,然后再去调用它,好了不多说了,想了解看上一篇吧

 

 

去除临时变量

正如我在前面提过的,临时变量可能是个问题。它们只在自己所属的函数中有效,所以它们会助长「冗长而复杂」的函数。这里我们有两个临时变量,两者都是用来从Customer 对象相关的Rental 对象中获得某个总量。不论ASCII 版或HTML 版都需要这些总量。我打算运用 Replace Temp with Query,并利用所谓的query method 来取代totalAmount 和frequentRentalPoints 这两个临时变量。由于class 内的任何函数都可以取用(调用)上述所谓query methods ,所以它能够促进较干净的设计,而非冗长复杂的函数:

首先:我们看回旧的代码

 

class Customer {
    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();
            // show figures for this rental
            result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "\n";
            totalAmount += each.getCharge();
        }
        // add footer lines
        result += "Amount owed is " + String.valueOf(totalAmount) + "\n";
        result += "You earned " + String.valueOf(frequentRenterPoints) + " frequent renter points";
        return result;
    }
}
 

 

我以Customer class 的getTotalCharge() 取代totalAmount 。

代码变成下面:

 

class Customer {
...
    /**
     * 通计清单
     * 
     * @return
     */
    public String statement() {
        int frequentRentePoints = 0;
        Enumeration<Rental> enu_rentals = rentals.elements();
        String result = "Rental Record for " + this.getName() + " \n";
        while (enu_rentals.hasMoreElements()) {
            Rental each = enu_rentals.nextElement();
            frequentRentePoints += each.getFrequentRenterPoints();
            result += "\t" + each.getMovie().getTitle() + "\t" + String.valueOf(each.getCharge()) + "\n";
        }
        result += "Amount owed is " + String.valueOf(getTotalCharge()) + "\n";
        result += "You earned " + String.valueOf(frequentRentePoints) + " frequent renter points";
        return result;
    }

    // 译注:此即所谓query method
    private double getTotalCharge() {
        double result = 0;
        Enumeration<Rental> enu_rentals = rentals.elements();
        while (enu_rentals.hasMoreElements()) {
            Rental each = (Rental) enu_rentals.nextElement();
            result += each.getCharge();
        }
        return result;
    }

}

 重构之后,重新编译并测试,然后以同样手法处理frequentRenterPoints。

 

class customer ...
public String statement() {
    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();
        //show figures for this rental
        result += "\t" + each.getMovie().getTitle()+ "\t" +

    }
    //add footer lines
    result +=  "Amount owed is " + String.valueOf(getTotalCharge()) + "\n";
    result += "You earned " + String.valueOf(frequentRenterPoints) +
      " frequent renter points";
    return result;
}
 

 

 

 

添加方法代码如下:

 

class customer ...
/**
 * 通计清单
 * 
 * @return
 */
public String statement() {
    Enumeration<Rental> enu_rentals = rentals.elements();
    String result = "Rental Record for " + this.getName() + " \n";
    while (enu_rentals.hasMoreElements()) {
        Rental each = enu_rentals.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;
}

// 此即所谓query method
private double getTotalCharge() {
    double result = 0;
    Enumeration<Rental> enu_rentals = rentals.elements();
    while (enu_rentals.hasMoreElements()) {
        Rental each = (Rental) enu_rentals.nextElement();
        result += each.getCharge();
    }
    return result;
}

// 此即所谓query method
private int getTotalFrequentRenterPoints() {
    int result = 0;
    Enumeration<Rental> enu_rentals = rentals.elements();
    while (enu_rentals.hasMoreElements()) {
        Rental each = (Rental) enu_rentals.nextElement();
        result += each.getFrequentRenterPoints();
    }
    return result;
}

 

 

图1.8至图1.11分别以UML class diagram(类图)和interaction diagram (交互作用图)展示statement() 重构前后的变化。

对比

 

 

想一想:

做完这次重构,有必要停下来思考一下。大多数重构都会减少代码总量,但这次却增加了代码总量,那是因为Java 1.1需要大量语句(statements)来设置一个总和(summing)循环。哪怕只是一个简单的总和循环,每个元素只需一行代码,外围的支持代码也需要六行之多。这其实是任何程序员都熟悉的习惯写法,但代码数量还是太多了。

这次重构存在另一个问题,那就是性能。原本代码只执行while 循环一次,新版本要执行三次。如果循环耗时很多,就可能大大降低程序的性能。单单为了这个原因,许多程序员就不愿进行这个重构动作。但是请注意我的用词:如果和可能。除非我进行评测(profile),否则我无法确定循环的执行时间,也无法知道这个循环是否被经常使用以至于影响系统的整体性能。重构时你不必担心这些,优化时你才需要担心它们,但那时候你已处于一个比较有利的位置,有更多选择可以完成有效优化

现在,Customer class 内的任何代码都可以取用这些query methods 了,如果系统他处需要这些信息,也可以轻松地将query methods 加入Customer class 接口。如果没有这些query methods ,其他函数就必须了解Rental class,并自行建立循环。在一个复杂系统中,这将使程序的编写难度和维护难度大大增加。

你可以很明显看出来,htmlStatement() 和statement() 是不同的。现在,我应该脱下「重构」的帽子,戴上「添加功能」的帽子。我可以像下面这样编写htmlStatement() ,并添加相关测试。

htmlStatement代码

 

class sustumer...
/**
 * 输出html清单
 * 
 * @return
 */
public String htmlStatement() {
    Enumeration<Rental> enu_rentals = rentals.elements();
    String result = "<H1>Rentals for <EM>" + getName() + "</EM></ H1><P>\n";
    while (enu_rentals.hasMoreElements()) {
        Rental each = (Rental) enu_rentals.nextElement();
        // show figures for each rental
        result += each.getMovie().getTitle() + ": " + String.valueOf(each.getCharge()) + "<BR>\n";
    }
    // add footer lines
    result += "<P>You owe <EM>" + String.valueOf(getTotalCharge()) + "</EM><P>\n";
    result += "On this rental you earned <EM>" + String.valueOf(getTotalFrequentRenterPoints())
            + "</EM> frequent renter points<P>";
    return result;
}

 通过计算逻辑的提炼,我可以完成一个htmlStatement() ,并复用(reuse)原本statements() 内的所有计算。我不必剪剪贴贴,所以如果计算规则发生改变,我只需在程序中做一处修改。完成其他任何类型的报表也都很快而且很容易。这次重构并不花很多时间,其中大半时间我用来弄清楚代码所做的事,而这是我无论如何都得做的。

 

前述有些重构码系从ASCII 版本里头拷贝过来——主要是循环设置部分。更深入的重构动作可以清除这些重复代码。我可以把处理表头(header)、表尾(footer)和报表细目的代码都分别提炼目出来。在 Form Template Method 实例中,.你可以看到如何做这些动作。但是,现在用户又开始嘀咕了,他们准备修改影片分类规则。我们尚未清楚他们想怎么做,但似乎新分类法很快就要引入,现有的分类法马上就要变更。与之相应的费用计算方式和常客积点计算方式都还待决定,现在就对程序做修改,肯定是愚蠢的。我必须进入费用计算和常客积点计算中,把「因条件 而异的代码」(译注:指的是switch 语句内的case 子句)替换掉,这样才能为 将来的改变镀上一层保护膜。现在,请重新戴回「重构」这顶帽子。

那么下面是完整代码:

Customer

 

package com.mkfree.refactoring.shap1;

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

/**
 * 顾客
 * 
 * @author hk
 * 
 *         2012-12-25 下午10:59:03
 */
public class Customer {

    private String name;
    private Vector<Rental> rentals = new Vector<>();

    public Customer(String name) {
        this.name = name;
    }

    /**
     * 添加
     * 
     * @param rental
     */
    public void addRentals(Rental rental) {
        rentals.add(rental);
    }

    public String getName() {
        return name;
    }

    /**
     * 输出html清单
     * 
     * @return
     */
    public String htmlStatement() {
        Enumeration<Rental> enu_rentals = rentals.elements();
        String result = "<H1>Rentals for <EM>" + getName() + "</EM></ H1><P>\n";
        while (enu_rentals.hasMoreElements()) {
            Rental each = (Rental) enu_rentals.nextElement();
            // show figures for each rental
            result += each.getMovie().getTitle() + ": " + String.valueOf(each.getCharge()) + "<BR>\n";
        }
        // add footer lines
        result += "<P>You owe <EM>" + String.valueOf(getTotalCharge()) + "</EM><P>\n";
        result += "On this rental you earned <EM>" + String.valueOf(getTotalFrequentRenterPoints())
                + "</EM> frequent renter points<P>";
        return result;
    }

    /**
     * 通计清单
     * 
     * @return
     */
    public String statement() {
        Enumeration<Rental> enu_rentals = rentals.elements();
        String result = "Rental Record for " + this.getName() + " \n";
        while (enu_rentals.hasMoreElements()) {
            Rental each = enu_rentals.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;
    }

    // 此即所谓query method
    private double getTotalCharge() {
        double result = 0;
        Enumeration<Rental> enu_rentals = rentals.elements();
        while (enu_rentals.hasMoreElements()) {
            Rental each = (Rental) enu_rentals.nextElement();
            result += each.getCharge();
        }
        return result;
    }

    // 此即所谓query method
    private int getTotalFrequentRenterPoints() {
        int result = 0;
        Enumeration<Rental> enu_rentals = rentals.elements();
        while (enu_rentals.hasMoreElements()) {
            Rental each = (Rental) enu_rentals.nextElement();
            result += each.getFrequentRenterPoints();
        }
        return result;
    }

}
 

 

 

Movie

 

package com.mkfree.refactoring.shap1;

/**
 * 电影类
 * @author hk
 *
 * 2012-12-25 下午10:55:14
 */
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 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;
    }

}

 

 

Rental

 

package com.mkfree.refactoring.shap1;

/**
 * 租凭
 * 
 * @author hk
 * 
 *         2012-12-25 下午10:57:00
 */
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;
    }

    /**
     * 
     * @return
     */
    int getFrequentRenterPoints() {
        if ((getMovie().getPriceCode() == Movie.NEW_RELEASE) && getDaysRented() > 1)
            return 2;
        else
            return 1;
    }
}

 

 Client

 

package com.mkfree.refactoring.shap1;

import org.junit.Test;

public class Client {

    @Test
    public void testStatement() {

        Movie movie1 = new Movie();
        movie1.setTitle("少林足球");
        movie1.setPriceCode(1);
        Rental rental1 = new Rental(movie1, 2);

        Movie movie2 = new Movie();
        movie2.setTitle("大话西游");
        movie2.setPriceCode(2);
        Rental rental2 = new Rental(movie2, 3);

        Customer customer = new Customer("oyhk");
        customer.addRentals(rental1);
        customer.addRentals(rental2);
        String statement = customer.statement();
        System.out.println(statement);
        System.out.println("------------------------------------------------");
        String htmlStatment = customer.htmlStatement();
        System.out.println(htmlStatment);
    }
}

 

 

最后输出的结果:有两个 1 statement 2 htmlStatement

  • 大小: 14.2 KB
  • 大小: 19.7 KB
  • 大小: 15.2 KB
  • 大小: 25.8 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics