《重构》增强代码可读性


在这里插入图片描述

重构原则

何为重构

不改变代码本身可观察的行为,进行代码的构造变化

为何重构

  1. 消除重复代码,方便未来的修改
  2. 利用重构协助理解不熟悉的代码
  3. 提高以后维护代码的速率

何时重构

  1. 添加代码时
  2. 修改bug时
  3. 复审代码质量时

重构会影响性能吗

可能会,但原则是先写出能完善的代码,再测试性能

实例

原始类

在这里插入图片描述

Movie类:该类主要记录类型、价格和标题等,是单纯数据类。

/**
 * Movie记录类型、价格和标题等,单纯数据类。
 */
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 Movie(String title, int priceCode) {
        this.title = title;
        this.priceCode = priceCode;
    }
 
    public int getPriceCode() {
        return priceCode;
    }
 
    public void setPriceCode(int priceCode) {
        this.priceCode = priceCode;
    }
 
    public String getTitle() {
        return title;
    }
}

Rental类:表示某位顾客租了一部影片,表示行为。

/**
 * Rental表示某位顾客租了一部影片,表示行为。
 */
class Rental {
    private Movie movie;
    private int daysRented;
 
    public Rental(Movie movie, int daysRented) {
        this.movie = movie;
        this.daysRented = daysRented;
    }
 
    public int getDaysRented() {
        return daysRented;
    }
 
    public Movie getMovie() {
        return movie;
    }
}

Customer类:表示顾客,有数据和相应的访问函数。

/**
 * Customer表示顾客,有数据和相应的访问函数
 */
public class Customer {
    private String name;
    private Vector rentals = new Vector();
 
    public Customer(String name) {
        this.name = name;
    }
 
    public void addRental(Rental rental) {
        rentals.add(rental);
    }
 
    public String getName() {
        return name;
    }
 
    /**
     * 提供一个用于生成详单的函数
     */
    public String statement() {
        double totalAmount = 0;
        //常客计算积分时使用
        int frequentRenterPoints = 0;
        Enumeration enumeration = rentals.elements();
        String result = "Rental Record for " + getName() + "\n";
        while (enumeration.hasMoreElements()) {
            //总金额
            double thisAmount = 0;
            Rental each = (Rental) rentals.elements();
            switch (each.getMovie().getPriceCode()) {
                case Movie.REGULAR:
                    thisAmount += 2;
                    //优惠力度
                    if (each.getDaysRented() > 2) {
                        thisAmount += (each.getDaysRented() - 2) * 1.5;
                    }
                    break;
                case Movie.NEW_RELEASE:
                    //果然还是新书最贵啊
                    thisAmount += each.getDaysRented() * 3;
                    break;
                case Movie.CHILDRENS:
                    thisAmount += 1.5;
                    if (each.getDaysRented() > 3) {
                        thisAmount += (each.getDaysRented() - 3) * 1.5;
                    }
                    break;
            }
            frequentRenterPoints++;
            //如果是新书,另算积分呢
            if (each.getMovie().getPriceCode() == Movie.NEW_RELEASE && each.getDaysRented() >= 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;
    }
}

进行重构

分解statements方法

长长的函数需要大卸八块,代码块越小,代码的移动和处理也就越轻松。将较小代码块移至更合适的类,降低代码重复使新函数更容易撰写。

提取函数

找出逻辑泥团并运用提取函数方法。本例中的switch语句需提炼至独立函数。找出函数内局部变量和参数。each(未被修改,可以当成参数传入新的函数)和thisAmount(会被修改,格外小心,如果只有一个变量修改,可以将其作为返回值)。那么将新函数返回值返回给thisAmount是可行的。

/**
 * 提供一个用于生成详单的函数
 */
public String statement() {
    ....
    while (enumeration.hasMoreElements()) {
        //总金额
        double thisAmount = 0;
        Rental each = (Rental) rentals.elements();
        thisAmount = amountFor(each);
        ....
    }
    ....
}
 
/**
 * 金额计算
 * @param aRental
 * @return
 */
private double amountFor(Rental aRental) {
    //注意double、int类型之间的转换。
    double result = 0;
    switch (aRental.getMovie().getPriceCode()) {
        case Movie.REGULAR:
            result += 2;
            //优惠力度
            if (aRental.getDaysRented() > 2) {
                result += (aRental.getDaysRented() - 2) * 1.5;
            }
            break;
        case Movie.NEW_RELEASE:
            //果然还是新书最贵啊
            result += aRental.getDaysRented() * 3;
            break;
        case Movie.CHILDRENS:
            result += 1.5;
            if (aRental.getDaysRented() > 3) {
                result += (aRental.getDaysRented() - 3) * 1.5;
            }
            break;
    }
    return result;
}
搬移函数

观察上一步提炼出来的amountFor函数,使用了Rental类的信息却没有使用来自Customer类的信息,函数是应该放在它所使用的数据的对象内的,所以amountFor应该要放到Rental类而非Customer类,调整代码以使用新类。

class Rental {
    ....
    /**
     * 金额计算
     * @return
     */
    public double getCharge() {
        //注意double、int类型之间的转换。
        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;
    }
}
public class Customer {
    ....
    /**
     * 提供一个用于生成详单的函数
     */
    public String statement() {
        ....
        while (enumeration.hasMoreElements()) {
            ...
            result += "\t" + each.getMovie().getTitle() + "\t" 
                 + String.valueOf(each.getCharge()) + "\n";
            totalAmount += each.getCharge();
        }
        ...
    }
}
提炼“积分计算”功能

积分计算因影片种类而有所不同,针对“积分计算”代码运用Extract Method重构手法。局部变量each,另一个临时变量是frequentRenterPoints(这个参数在使用之前已初始化,但提炼出的函数并未读取该值,因此无需传入,只需作为新函数的返回值累加上去即可)。

class Rental {
     ...
    /**
     * 计算常客积分
     * @return
     */
    public int getFrequentRenterPoints() {
        //如果是新书,另算积分呢
        if (getMovie().getPriceCode() == Movie.NEW_RELEASE && getDaysRented() >= 1) {
            return 2;
        } else {
            return 1;
        }
    }
}
public class Customer {
    ....
    /**
     * 提供一个用于生成详单的函数
     */
    public String statement() {
        double totalAmount = 0;
        //常客计算积分时使用
        int frequentRenterPoints = 0;
        Enumeration enumeration = rentals.elements();
        String result = "Rental Record for " + getName() + "\n";
        while (enumeration.hasMoreElements()) {
            Rental each = (Rental) rentals.elements();
            //计算常客积分
            frequentRenterPoints += each.getFrequentRenterPoints();
            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;
    }
}
去除临时变量(以查询取代临时变量)

临时变量会造成冗长复杂的函数,以查询取代临时变量方法,以查询函数替代totalAmount和frequentRentalPoints临时变量。任何函数均可调用,促成干净设计、减少冗长函数。

public class Customer {
    ...
    /**
     * 提供一个用于生成详单的函数
     */
    public String statement() {
        //常客计算积分时使用
        Enumeration enumeration = rentals.elements();
        String result = "Rental Record for " + getName() + "\n";
        while (enumeration.hasMoreElements()) {
            Rental each = (Rental) rentals.elements();
            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;
    }
 
    /**
     * 获取总积分
     * @return
     */
    private double getTotalFrequentRenterPoints() {
        int result = 0;
        Enumeration enumeration = rentals.elements();
        while (enumeration.hasMoreElements()) {
            Rental each = (Rental) rentals.elements();
            result += each.getFrequentRenterPoints();
        }
        return result;
    }
 
    /**
     * 获取总金额
     * @return
     */
    private double getTotalCharge() {
        double result = 0;
        Enumeration enumeration = rentals.elements();
        while (enumeration.hasMoreElements()) {
            Rental each = (Rental) rentals.elements();
            result +=  each.getCharge();
        }
        return result;
    }
}
运用多态取代与价格相关的条件逻辑
代码迁移Movie类

用户准备修改影片分类规则。费用计算和常客积分计算也会因此而发生改变。因为系统可能会加入新影片类型,不稳定,因此在Movie对象内计算费用。同样的手法处理常客积分函数。

public class Movie {
   ...
    /**
     * 根据影片类型获取费用
     * @param daysRented
     * @return
     */
    public 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;
    }
 
    public int getFrequentRenterPoints(int daysRented) {
        //如果是新书,另算积分呢
        if (getPriceCode() == Movie.NEW_RELEASE && daysRented >= 1) {
            return 2;
        } else {
            return 1;
        }
    }
}
class Rental {
    private Movie movie;
    private int daysRented;
    ...
    /**
     * 金额计算
     * @return
     */
    public double getCharge() {
        return movie.getCharge(daysRented);
    }
 
    /**
     * 计算常客积分
     * @return
     */
    public int getFrequentRenterPoints() {
        return movie.getFrequentRenterPoints(daysRented);
    }
}
Price类 状态模式

多态取代switch语句,多态设计时不要直接继承Movie,而是通过Price间接去处理,一部影片可以在生命周期内修改自己的分类,但一个对象却不能再生命周期内修改自己所属的类,使用state模式

abstract class Price {
    abstract  int getPriceCode();
}
public class ChildrenPrice extends Price{
    @Override
    int getPriceCode() {
        return Movie.CHILDRENS;
    }
}
public class NewReleasePrice extends Price {
    @Override
    int getPriceCode() {
        return Movie.NEW_RELEASE;
    }
}
public class RegularPrice extends Price {
    @Override
    int getPriceCode() {
        return Movie.REGULAR;
    }
}
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 Price price;
 
    public Movie(String title, int priceCode) {
        this.title = title;
        setPriceCode(priceCode);
    }
 
    public int getPriceCode() {
        return price.getPriceCode();
    }
 
    public void setPriceCode(int arg) {
        switch (arg) {
            case Movie.REGULAR:
                price = new RegularPrice();
                break;
            case Movie.NEW_RELEASE:
                price = new NewReleasePrice();
                break;
            case Movie.CHILDRENS:
                price = new ChildrenPrice();
                break;
            default:
                throw new IllegalArgumentException("Incorrect Price Code");
        }
    }
    ....
    }
 }
搬移函数

将Movie中的getCharge方法下沉至Price方法中。

abstract class Price {
    abstract int getPriceCode();
    /**
     * 根据影片类型获取费用
     * @param daysRented
     * @return
     */
    public 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;
    }
}
public class Movie {
    private Price price;
    ...
 
    public int getPriceCode() {
        return price.getPriceCode();
    }
    ....
}
以多态取代表达式

次取出getPriceCode的一个case分支,在对应的类建立覆盖函数。同样的方法处理getFrequentRenterPoints方法

/**
 * 新建Price类,并提供类型相关的行为,为此,加入抽象函数,并在所有子类中加上对应的具体操作。
 */
abstract class Price {
    /**
     * 获取影片类型code码
     * @return
     */
    abstract  int getPriceCode();
    /**
     * 根据影片类型获取费用
     * @param daysRented
     * @return
     */
    abstract double getCharge(int daysRented);
 
    /**
     * 如果是新书,采用复写的方法,在超类中留下一个已定义的函数,使之成为一种默认行为。
     * @param daysRented
     * @return
     */
    int getFrequentRenterPoints(int daysRented) {
        return 1;
    }
}
public class RegularPrice extends Price {
    @Override
    int getPriceCode() {
        return Movie.REGULAR;
    }
 
    @Override
    public double getCharge(int daysRented) {
        double result = 2;
        //优惠力度
        if (daysRented > 2) {
            result += (daysRented - 2) * 1.5;
        }
        return result;
    }
}
public class NewReleasePrice extends Price {
    @Override
    int getPriceCode() {
        return Movie.NEW_RELEASE;
    }
 
    @Override
    public double getCharge(int daysRented) {
        //果然还是新书最贵啊
        return daysRented * 3;
    }
 
    @Override
    public int getFrequentRenterPoints(int daysRented) {
        return (daysRented > 1) ? 2 : 1;
    }
}
public class ChildrenPrice extends Price{
    @Override
    int getPriceCode() {
        return Movie.CHILDRENS;
    }
 
    @Override
    public double getCharge(int daysRented) {
        double result = 1.5;
        if (daysRented > 3) {
            result += (daysRented - 3) * 1.5;
        }
        return result;
    }
}

重构之后的类图
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值