重构,第一个案例

第一个案例,太简单也不好,太复杂也不好。

1.1 起点

“影片出租店用的程序”,计算程序租了哪些影片,租期多长,可以计算费用积分。影片分为几类,积分根据影片是否为新片而不同。

UML 图很简单。首先定义三个实体类,影片、租赁、顾客,各自的方法。再加一个 生成详单的方法。

public String statement(){
        double totalAmount = 0;
        int frequentRenterPoints = 0;
        Enumeration rentals = _rentals.elements();
        String result = "Rental Record for " + getName()+ "\n";
        while (rentals.hasMoreElements()){
            double thisAmount = 0;
            Rental each = (Rental)rentals.nextElement();
            //determine amounts for each line
            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;
            }
            // add frequent renter points
            frequentRenterPoints ++;
            // add bonus for a two day new release rental
            if ((each.getMovie().getPriceCode()== Movie.NEW_RELEASE)&&
                each.getDaysRented()> 1)frequentRenterPoints ++;
            //show figures for this rental
            result += "\t" + each.getMovie().getTitle()+ "\t" +
                String.valueOf(thisAmount)+ "\n";
            totalAmount += thisAmount;
        }
            //add footer lines
        result += "Amount owed is " + String.valueOf(totalAmount)+ "\n";
        result += "You earned " + String.valueOf(frequentRenterPoints)+
            " frequent renter points";
        return result;
    }

相比之下这个方法很简单,也很清晰,算是个好代码吗?

重构

其实我们应该通过自己的思考获得知识,而不是通过学习。学习到的只是表面属于你,思考的知识才是真的属于你。所以我只是大概看了一下书,然后自己重新推导一下,遇到不会的了,再回去看书。

switch if 语句处理

首先我觉得 switch 语句现需要进行更改。我希望吧 switch 变成多态的方式实现,不只是switch,一些if else 也可以通过多态方式实现。

我们看出这段代码主要是 根据 Rental each 计算 thisAmount。而且都是直接加,所以可以不用将 thisAmount 作为参数只需要将 each 作为参数,根据 each 计算 thisAmount 的逻辑和电影种类有关系。 因为 switch 的判断条件是 movie相关,所以实现方式就是定义三个 Movie 的子类。每个子类都有一个计算 thisAmount 增量的方法,方法参数是Rental each。逻辑就是复制这里的三段逻辑。

这样的话引进一个问题,现在 Movie 变成了抽象类,我们就需要修改之前 new Movie 的代码,改的方法可以使用工厂模式,但是还是需要判断 PriceCode,根据不同的值 new 不同对象。 然后这个 switch 语句就可以换成 each.getMovie().getPrice(each) 了。

这样是有可能问题的,第一个问题是我们新建了三种 Movie ,实际上这只是它的 pricecode,不能因为 pricecode变了movie 就变了,另外,movie 一旦new了类型不能变,但是电影的类型是可能变的。 所以我们需要借助 状态模式/策略模式,不是new 三个 Movie ,二是new 三个 PriceCode。 实现方式比上面的操作可能更简单一点,因为都不需要修改 Movie 的构造方法。直接修改 Movie 构造方法,将 PriceCode 赋值变成 new 三个不同的 对象。然后对应的 getprice 调用 PriceCode 的方法即可。

另外有一点就是抽象出来的方法,为什么将 Rental each 作为参数传入而不是 pricecode,因为 pricecode 是可以变得东西。我们需要在 pricecode 计算费用。

同理 frequentRenterPoints 计算和 thisAmount 一样,和 Movie 种类有关,所以也可以吧 后面的 if 语句放入 pricecode 中。

这就是我的处理方法,用 多态/state/strategy 替换 switch。

模板分解

我们发现其实 thisAmount 和 frequentRenterPoints 是我们计算的主要逻辑,返回结果基于他们拼凑。所以他们两个的逻辑可以放到两个单独的方法,顺便可以改一下变量名。

这样带来一个副作用,因为计算是需要循环遍历,本身只需要循环一次,现在需要循环三次了。

当我们移动以后,可以发现提炼出来的某些方法完全不应该属于这个类,可以放到另一个类中。然后我们可能发现某些局部变量变得多余,可以移除。