美文网首页程序员首页投稿(暂停使用,暂停投稿)
阅读《重构》之一:重要的第一个例子

阅读《重构》之一:重要的第一个例子

作者: FindCrt | 来源:发表于2017-09-10 16:34 被阅读569次

    第一个例子很重要,因为它通过实际操作带你走进什么是重构,为何重构可以带来实用的价值。作者也在书开头说了,理论容易让他昏昏入睡,一个好的例子能带来更好的理解,他做到了。

    这是一个什么例子?

    是一个用户租聘录像带的小程序,包含3个类:用户、租聘和录像带,它们的关系如下:customer --> rental --> movie。然后核心的业务是计算用户租聘的价格和加分,并生成订单输出。

    最初始的代码是把这个生成订单的逻辑全部集中在一个statement函数里,内容大致如下:

    public String statement(){
        double totalAmount = 0; //此次租聘总价
        int rentalPoints = 0;   //此次租聘积分
        String result = "Rental Record for xxx";//租聘订单内容
        
        for(each in rentals){ //(1)
            double thisAmount = 0;
            switch(each.getMovie().getPriceCode()){
                case xxx
                case xxx
                //根据影片类型计算价格
                thisAmount = xxx
            }
            
            //根据类型计算用户积分(2)
            rentalPoints += xxx
            
            //添加这个影片内容输入到订单(3)
            result += xxx
        }
    }
    

    首先这个程序并没有什么问题,看上去,但是为了让程序更健壮、更容易应对变化(这也是重构的最重要的目的之一),我们需要对未来可能的改变做一些假设判断。

    首先分析一个整个业务,主要的内容为:

    • 计算价格
    • 计算积分
    • 生成订单内容,目前是纯文字类型。

    那么可能的改变就有:

    • 来了新类型影片,价格和积分计算都不同已有类型
    • 已有类型的计算方式发生变化,比如店开不下去了,或者临时促销等等
    • 改用HTML方式输出订单,甚至改成生成图片发送给用户等等。

    这些都会导致statement这个函数的改变,然而它们却是不同动机引发的。为了让修改集中在更小的逻辑范围里,需要对statement进行拆分。

    改进1

    把对影片价格的计算移动到单独的函数里。也就是(1)位置switch部分。这一步就可以应对增加新类型或者旧类型价格计算方式改变。这些改变都会在单独的新函数里修改,而不会干扰到statement函数。

    改进2

    当把影片价格的计算移动到单独函数去之后,会发现这个函数并没有用到当前类customer的任何信息,它的逻辑完全是依赖于rental这个类的(一个rental代表一个影片的租聘,和影片是一对一的关系)。

    这也是书里提及的最重要的重构标识之一:当一个函数更多的依赖于另一个类而不是当前类的时候,应该考虑把这个函数移动到那个依赖更多的类中。

    所以在Rental类中添加double getCharge()函数,这样最开始的switch部分就改为了:
    thisAmount = each.getCharge().

    做完这一步,那么影片类型和价格计算的变化,不仅不会影响到statement函数,甚至不会影响到customer这个类。

    改进3

    积分计算和上面价格计算一样,也拆分到Rental类里面去。

    改进4

    书里接下来的改进是把statement函数里的循环都拆了,循环存在的目的是为计算总体的价格和积分,总价格计算放到一个新函数getTotalCharge里,总积分计算方法新函数getTotalFrequentRentalPoints里。

    书里给的理由是减少临时变量,但我觉得不是重点,结合后面(p32)的这一句话:如果没有这些查询函数,其他函数就必须了解Rental类,并自行建立循环。这里有几点非常重要:

    • 首先根本的原因是需求。其他地方也需要总价格、总积分这些,比如你的程序有5个地方用到积分计算,而他们需要的都是总积分,那么一个单独的用来计算总积分的函数就变得非常需要。一个函数要还是不要,关键看需求。

    • 我一直认为模块封装就要像黑盒子一样。你提供了需要的函数,满足外界需要的任何需求,那么外界就不需要了解你的内部,对方也就能够安心的干自己的事。如果一个小需求需要你把整个程序的源码全部读一遍,那肯定是又浪费时间,又很容易干扰到其他部分。

    这一次改进后,任何其他地方需要用到总价格或总积分,它不需要知道任何细节,到底是循环还是不循环,各种价格如何计算或者哪些类型影片不计入积分等等,它只需要调用getTotalCharge,一切ok!

    改进5

    从改进2那里可知,其实仔细想,价格和积分的计算更多依赖于movie类,对rental的依赖只有租聘的天数,所以把它们进一步移动到movie才是对的。如rental类变为:

    class Rental
    double getCharge(){
        return _movie.getCharge(_daysRented);
    }
    
    改进6

    接下来有两点改进:1. 引入state模式 2.使用多态代替switch.

    什么是使用多态代替继承?

    首先多态是obj.method1()会因为obj的类型不同而调用不同的方法,在计算影片的价格和积分时,同样因为影片类型不同而进行不同的操作,这正好符合多态的行为方式。

    修改之前是:

    class Movie
    double getCharge(){
        switch(type){
            case 1:
                xxx
                break;
            case 2:
                xxx
                break;
            .....
        }
    }
    

    修改之后变为:

    class Type1Movie
    double getCharge(){
        type1的计算方式
    }
    
    class Type2Movie
    double getCharge(){
        type2的计算方式
    }
    ......
    

    不同的计算方式分散到不同的子类里去了,而外界调用的时候却没有改变,还是movieObj.getCharge()

    这种手段可以很好的应对新增或删除类型,新增类型只需要添加一个新类,实现getCharge方法,就一切正常运转了,原有的类甚至不知道新增或者删除了一个类。

    这样就有了下图的结构

    movie继承.JPG

    然后是使用state模式,修改后的类图:

    state模式movie继承.JPG

    可以看到是: 把价格计算单独抽离做了新的类price,然后price根据不同计算方式构建继承体系。

    state模式是设计模式那本书里提的,我的理解是:类的行为受到某个属性的影响,当这个影响变得复杂之后,比如要做许多的判断,可以把这个属性抽离作为状态类,把相关的行为搬移到状态类里。

    其实从这里可以看出,继承也是可以达到减轻状态判断的,那么state模式的意义何在?这里有一个问答,虽然问的是继承和strategy模式的区别,但也可以理解到state模式上。简单说,如果一个类,有多种影响行为的属性,全部继承,那么子类数量将为相当巨大。比如属性1有4种状态,属性2有5种状态,那么子类就有20个了。而采用state模式,可以让各种state自由组合,更方便。

    书里提到使用state模式的只有一句话:一步影片可以在生命周期内修改自己的分类,一个对象却不能在生命周期内修改自己所属的类。就是说影片的类型在逻辑上是变化的,而如果使用类继承策略,那么某个影片对象会因为无法修改自己的类而无法修改影片类型。

    使用继承体系,那么逻辑上的类型就和程序里的类绑定了,而如果逻辑上是可变的,那么就产生了冲突。

    而采用state模式,就可以化解这个问题,只需切换不同的属性对象,就拥有了不同的类型。就像。。。自行车装上了电动马达就变成了电动车了。

    最后:movie对象拥有price对象,price根据计算方式拆分不同子类,使用多态进行不同方式价格计算。

    最终类图.JPG

    相关文章

      网友评论

        本文标题:阅读《重构》之一:重要的第一个例子

        本文链接:https://www.haomeiwen.com/subject/abwvrxtx.html