改善既有代码的设计(一)----------小案例展示重构的意义

最近在看一本书,关于重构的,改善既有代码的设计,感谢作者给我们翻译了这本还不错的书。

本书很好的一点就是上来没有讲历史渊源这一类的催人入睡的课题,而是先用一个小案例来展示重构的过程和意义,这也是我看着本书没有止于前言的主要原因,看完了本案例,才会觉得代码真是一项艺术,与难度无关,更多的好像与强迫症有关似的,初期开发是成型,后期重构是雕琢,这也推翻了我以前的想法,比如注释要尽量写的多啊,重构基本需要推到重做啊,其实感觉本书更像是给代码习惯和编程风格一个规范化,以前一些模棱两可的习惯在这里会有一个比较严谨的分析。

好了,不说废话了,翠花,上案例。

实例非常简单,就是一个影片出租店用的程序,计算每一位顾客的消费金额并打印详单,操作者告诉程序:顾客租了哪些影片,租期多长,程序根据影片类型和租赁时间计算出费用,和为会员计算积分,影片类型:普通片,儿童片,和新片,积分根据影片是否为新片而有所不同。

首先,得有三个类:Movie   Rental  Customer

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 arg){
        priceCode=arg;
    }
    public String getTitle(){
        return title;
    }

}
public class Rental {
    private com.demo.zxp.demo1.Movie movie;
    private int daysRented;

    private Rental(Movie movie,int daysRented){
        this.movie=movie;
        this.daysRented=daysRented;
    }

    public int getDaysRented(){
        return daysRented;
    }

    public Movie getMovie(){
        return movie;
    }

}

public class Customer1{
    private String name;
    private Vector _rentals=new Vector();
    private Customer1(String name){
        this.name=name;
    }
    public void addRental(Rental rental){
        _rentals.addElement(rental);
    }
    public String getName(){
        return name;
    }
    public String statement() {
        double totalAmount = 0;
        int frequentRentalPoints = 0;
        Enumeration rentals = _rentals.elements();
        String result = "Rental Record for" + getName() + "\n";
        while (rentals.hasMoreElements()) {
            double thisAmount = 0;
            Rental each = (Rental) rentals.nextElement();

            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;

            }
            //计算积分
            frequentRentalPoints++;
            if (each.getMovie().getPriceCode() == Movie.NEW_RELEASE && each.getDaysRented() > 1) {
                frequentRentalPoints++;
            }

            result+="\t"+each.getMovie().getTitle()+"\t"+String.valueOf(thisAmount)+"\n";
            totalAmount+=thisAmount;
        }
        result += "应收总金额是" + String.valueOf(totalAmount) + "\n";
        result += "您赚取了" + String.valueOf(frequentRentalPoints) + "积分";
        return result;

    }
}

这个程序设计的如何呢,很明显不符合面向对象的精神,其实这样一个小程序写成这样,可能也没啥大不了的但是如果他是一个复杂系统的一部分,那这个复杂系统的前途就危险了,在这个例子里

关于这个statement函数,太过于庞大,如果用户希望对系统进行修改一下,希望以HTML的格式输出详单,直接在网页上显示,现在想一下,这个变化会带来什么影响,看看代码就发现这个函数根本不能复用,唯一能做的就编写一个html()函数大量复用statement中的行为,当然现在做这个还不费力,如果计费标准发生变化呢,就必须同时修改statement()和html()函数了,并且还要确保两处修改一致,当后续还需要修改的时候,复制粘贴带来的问题就浮现了,如果用户还希望改变影片的分类规则呢,但是还没决定好怎么改,他们想到了几种方案,都会影响到积分和消费额的计算,作为一个开发者,我们可以肯定的就是:  
  不论用户提出什么方案,我们唯一能够获得保证就是他们一定会在六个月之内在此修改它。

重构第一步:就是为即将修改的代码建立一个可靠的测试环境,

2 分解并重组statement()

代码块越小,代码的功能越容易管理,代码的处理和移动就越轻松,首先,找出代码的逻辑泥团,并运用  Extract Method ,要在这段代码里找出函数内的局部变量和参数,任何不会被修改的变量可以当做参数传入新函数,至于被修改的就需要小心一点,如果只有一个变量会被修改,可以死把它当做函数返回值,

下面是重构后的代码

public class Customer {
    private String name;
    private Vector _rentals=new Vector();
    private Customer(String name){
        this.name=name;
    }
    public void addRental(Rental rental){
        _rentals.addElement(rental);
    }
    public String getName(){
        return name;
    }
    public String statement() {
        double totalAmount = 0;
        int frequentRentalPoints = 0;
        Enumeration rentals = _rentals.elements();
        String result = "Rental Record for" + getName() + "\n";
        while (rentals.hasMoreElements()) {
            double thisAmount = 0;
            Rental each = (Rental) rentals.nextElement();

            thisAmount=amountFor(each);
            frequentRentalPoints++;

            if (each.getMovie().getPriceCode() == Movie.NEW_RELEASE && each.getDaysRented() > 1) {
                frequentRentalPoints++;
            }
            result+="\t"+each.getMovie().getTitle()+"\t"+String.valueOf(thisAmount)+"\n";
            totalAmount+=thisAmount;
        }
        result += "应收总金额是" + String.valueOf(totalAmount) + "\n";
        result += "您赚取了" + String.valueOf(frequentRentalPoints) + "积分";
        return result;

    }
    private double amountFor(Rental each){
        double thisAmount=0;
        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;


        }
        return thisAmount;
    }
}
现在测试完没问题以后,就可以修改自己不喜欢的变量名字了,each-->aRental      thisAmount--> result

观察这个函数,我们使用了Rental类的信息,却没有使用Customer类的信息,这就立刻值得怀疑,它是否被放错了位置,函数应该放在它所使用的数据的所属对象内,所以这个函数应该搬到Rental类中,运用Move Method 

public class Rental {
    private Movie movie;
    private int daysRented;

    private Rental(Movie movie,int daysRented){
        this.movie=movie;
        this.daysRented=daysRented;
    }

    public int getDaysRented(){
        return daysRented;
    }

    public Movie getMovie(){
        return movie;
    }

    private double amountFor(){
        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;
    }
}
此时,Customer类中的函数委托调用新函数即可(如果 我们不想修改对外的调用)

private double amountFor(Rental aRental){
        
        return aRental.amountFor();
    }
找到旧函数所有的调用点,修改 

thisAmount=each.getCharge();
下一个应该引起我们注意的是。thisAmount变得多余了,他接受了函数的赋值以后就没有再变化过,所以这里运用 Replace Temp with Query 把thisAmount 除去

 public String statement() {
        double totalAmount = 0;
        int frequentRentalPoints = 0;
        Enumeration rentals = _rentals.elements();
        String result = "Rental Record for" + getName() + "\n";
        while (rentals.hasMoreElements()) {
            Rental each = (Rental) rentals.nextElement();

            frequentRentalPoints++;

            if (each.getMovie().getPriceCode() == Movie.NEW_RELEASE && each.getDaysRented() > 1) {
                frequentRentalPoints++;
            }
            result+="\t"+each.getMovie().getTitle()+"\t"+String.valueOf(each.amountFor())+"\n";
            totalAmount+=each.amountFor();
        }
        result += "应收总金额是" + String.valueOf(totalAmount) + "\n";
        result += "您赚取了" + String.valueOf(frequentRentalPoints) + "积分";
        return result;

    }
临时变量往往容易引发问题,他们会导致大量参数被传来传去,而其实完全没有这种必要,因为你很容易跟丢他们,尤其是在长长的函数之中更是如此,当然,我们也需要付出性能上的代价,例如,本例子中费用的计算就被 计算了两次,但是这也很容易在Rental中被优化,而且如果代码有合理的组织和管理,优化就会有很好的效果,关于这个问题后边会有作者的详细解释

下一步对会员积分的计算做相似处理

Rental类中

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

去除临时变量

目前临时变量有两个 totalAmount 和frequentRentalPoints ,运用Replace Temp with Query 

public class Customer {
    private String name;
    private Vector _rentals=new Vector();
    private Customer(String name){
        this.name=name;
    }
    public void addRental(Rental rental){
        _rentals.addElement(rental);
    }
    public String getName(){
        return name;
    }
    public String statement() {
        Enumeration rentals = _rentals.elements();
        String result = "Rental Record for" + getName() + "\n";
        while (rentals.hasMoreElements()) {
            Rental each = (Rental) rentals.nextElement();
            result+="\t"+each.getMovie().getTitle()+"\t"+String.valueOf(each.getCharge())+"\n";
        }
        result += "应收总金额是" + String.valueOf(getTotalCharge()) + "\n";
        result += "您赚取了" + String.valueOf(getTotalFrequentRenterPoints()) + "积分";
        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;
    }
    private double getTotalFrequentRenterPoints(){
        double result=0;
        Enumeration rentals = _rentals.elements();
        while (rentals.hasMoreElements()) {
            Rental each = (Rental) rentals.nextElement();
            result+=each.getFrequentRenterPoints();
        }
        return result;
    }

}

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

现在Customer类内任何代码都可以调用这些查询函数了,如果系统其它部分需要这些信息,也可以轻松的将查询函数加入Customer类的接口。如果没有这些查询函数,其它函数就必须了解Rental类,并自行简历循环,在一个复杂的系统中,这将使程序的编写难度和维护难度大大增加,所以如果现在客户要求详单一html的格式输出,我们在编写htmlStatement()函数的时候,就不必剪贴复制了,所以如果计算规则发生了改变,只需要在程序中一处做修改,完成其他类型的详单也都是很快很容易的事情了,而这时我们无论如何都得做的。

运用多态取代与价格相关的条件逻辑

这个问题的一部分就是switch语句,最好不要在另一个对象的属性基础上运用switch语句,如果不得不使用,也要在自己的数据上使用,这就暗示getCharge()方法需要移动到Movie类中,为了让它得以运作,必须把租期长度作为参数传递过去,当然,租期长度来自Rental类,计算费用的时候需要两项数据:租期长度和影片类型,为什么选择将租期长度传给Movie对象,而不是将影片类型喜欢递给Rental对象呢,因为本系统可能发生变化的是加入新影片类型,这种变化带有不稳定倾向,如果影片类型有所变化,我们希望尽量控制它造成的影响,所以选择在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 arg){
        priceCode=arg;
    }
    public String getTitle(){
        return title;
    }
    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;
        }
    }
}

public class Rental {
    private Movie movie;
    private int daysRented;

    private Rental(Movie movie,int daysRented){
        this.movie=movie;
        this.daysRented=daysRented;
    }

    public int getDaysRented(){
        return daysRented;
    }

    public Movie getMovie(){
        return movie;
    }

    public double getCharge(){

        return movie.getCharge(daysRented);
    }
    public int getFrequentRenterPoints(){
       return movie.getFrequentRenterPoints(daysRented);
    }
}

终于来到继承

我们有数种影片类型,他们一不同的方式回答相同的问题,这听起来很像子类的工作,我们可以建立Movie的三个子类,每个都有自己的计费方法,这么一来就可以用多态来取代switch语句了,很遗憾这里有个小小的问题,一部影片可以在生命周期内修改自己的分类,一个对象却不能再生命周期内修改自己所属的类,不过还有一个解决办法:State模式,加入一层间接性,Price,我们可以再Price对象内进行子类动作化

那这个算是State还是Strategy,取决于Price类究竟代表计费方式(PriceStrategy)还是代表影片的某个状态,在这个阶段,对于模式名称的选择反映出你对结构的想法,此时我把他视为影片的某种状态,如果未来我觉得Strategy模式能更好的说明我的意图,我会再重构他,修改名字,以形成Strategy

为了引入State模式,使用了三个重构方法。首先运用Replace Type Code with State将与类型相关的行为搬移到State模式内,然后运用Move Method将switch语句移到Price类,最后运用Replace Conditional with Polymorphism 去掉switch语句

第一步针对类型代码使用Self Encapsulate Field ,确保任何时候都通过取值函数和设置函数来访问类型代码,多数访问操作来自其他类,他们已经在使用取值函数,但是构造函数仍然直接访问价格代码,这里可以用一个设置函数来替代,

public class Movie ......
    public Movie(String title,int priceCode){
        this.title=title;
//        this.priceCode=priceCode;
        setPriceCode(priceCode);
    }

现在新建一个Price类,并在其中提供类型相关的行行为,为了实现这一点,在Price类中加入一个抽象函数,并在所有子类中加上对应行为:

public abstract class Price {
    abstract int getPriceCode();
}
public class ChildrensPrice 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;
    }
}

现在需要修改Movie类内的“价格代号”访问函数,这就意味着必须在Movie类中保存一个Price对象,而不再是一个price_Code变量,此外,还要修改访问函数

public class Movie ......
    
//    private int priceCode;
    private Price price;

    public Movie(String title,int priceCode){
        this.title=title;
//        this.priceCode=priceCode;
        setPriceCode(priceCode);
    }


    public int getPriceCode(){
        return price.getPriceCode();
    }

    public void setPriceCode(int arg){
        switch (arg){
            case REGULAR:
                price=new RegularPrice();
                break;
            case CHILDRENS:
                price=new ChildrensPrice();
                break;
            case NEW_RELEASE:
                price=new NewReleasePrice();
                break;
            default:
                throw new IllegalArgumentException("Incorrect Price Code");
        }
    }
接下来就是要对getCharge方法实施Move Method,
public class Movie ......
 
    public double getCharge(int daysRentend){
        return price.getCharge(daysRentend);
    }

将getCharge方法具体实现搬到Price类中,这里就不贴代码了,继续,运用Replace Conditional with Polymorphism ,做法是一次取出一个分支,在相应的类建立一个覆盖函数,

这里就把是三个分支都弄了吧,毕竟这样贴代码好累 啊

public class RegularPrice extends Price{
    @Override
    int getPriceCode() {
        return Movie.REGULAR;
    }
    public double getCharge(int daysRented){
        double result=2;
        if ( daysRented> 2) {
            result += (daysRented - 2) * 1.5;

        }
        return result;
    }
}

public class ChildrensPrice extends Price{
    @Override
    int getPriceCode() {
        return Movie.CHILDRENS;
    }
    public double getCharge(int daysRented){
        double result=1.5;
        if ( daysRented> 3) {
            result += (daysRented - 3) * 1.5;

        }
        return result;

public class NewReleasePrice extends Price{
    @Override
    int getPriceCode() {
        return Movie.NEW_RELEASE;
    }
    public double getCharge(int daysRented){

        return daysRented * 3;
    }
    
}

处理完所有的分支以后,就把Price.getChar()声明为abstract,

public abstract class Price ......
    abstract int getPriceCode();
    public abstract double getCharge(int daysRented);
然后同样的手法处理getFrequentRenterPoints(),首先把这个函数移动到Price类,但是这一次不会声明成abstract,只为新片类型增加一个覆写函数,并在父类内留下一个已定义的函数,使他成为一种默认的行为

ublic abstract class Price {
    abstract int getPriceCode();
    public abstract double getCharge(int daysRented);
    public int getFrequentRenterPoints(int daysRentend){

            return 1;
    }
}

public class NewReleasePrice extends Price{
    @Override
    int getPriceCode() {
        return Movie.NEW_RELEASE;
    }
    public double getCharge(int daysRented){

        return daysRented * 3;
    }
    public int getFrequentRenterPoints(int daysRentend){

        return (daysRentend>1) ? 2:1;
    }
}

好的,圆满结束,引入State模式花了作者不少力气,(我这一通敲,也是累够呛)值得吗?这么做的收获就是:如果以后要修改任何与价格有关的行为,或者是添加新的定价标准,或者是加入其它取决于价格的行为,程序的修改会容易的多,这个程序的其它部分并不知道我运用了State模式,对于我目前拥有的这么几个小量行为来说,任何功能或者特性上的修改也许都不合算,但如果在一个更复杂的系统中,有十多个与价格相关的函数,程序修改的难易度就会有很大区别,以上修改都是小步骤进行,进度四惠过慢,但是基本一次都没有打开调试器,所以整个过程很快就过去了。

结语:

作者希望用这个简单的例子让大家对代码重构有一点感觉,里边提到了很多重构手法,Extract Method(提炼函数) ,Move Method(搬移函数) ,Replace Conditional with Polymorphism (以多态取代条件表达式),Self Encapsulate Field (自封装字段),ReplaceType Code with State/Strategy(State/Strategy取代类型代码),这些重构行为都让责任分配更合理,代码维护更轻松。

因为我是这个本书的搬运者,所以在措辞和表达上想尽力表达出作者的最原始意图,所以描述的有点繁琐了,当我们熟悉了整个重构的套路,对程序的修改就会变得快多了,因为至少你不用想写博客的事情,也不用想别人知不知道你再用什么方法来重构。

通过读了这本书,对重构的理解感觉就像是教师的工作,纯粹是一种良心活,因为也许你做完一个版本就走了,这个程序的后期维护就与你没关系了,而你还要去为了后续人的方便来一遍一遍的review自己的代码,重构它,这良心,也是没谁了,所以提倡大家对代码有一种洁癖感,写一行有质量的代码,功德无量。

撒花,结束

对错别字真是忍不了啊



  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第1章 重构,第一个案例 1 1.1 起点 1 1.2 重构的第一步 7 1.3 分解并重组statement() 8 1.4 运用多态取代与价格相关的条件逻辑 34 1.5 结语 52 第2章 重构原则 53 2.1 何谓重构 53 2.2 为何重构 55 2.3 何时重构 57 2.4 怎么对经理说 60 2.5 重构的难题 62 2.6 重构设计 66 2.7 重构与性能 69 2.8 重构起源何处 71 第3章 代码的坏味道 75 3.1 Duplicated Code(重复代码) 76 3.2 Long Method(过长函数) 76 3.3 Large Class(过大的类) 78 3.4 Long Parameter List(过长参数列) 78 3.5 Divergent Change(发散式变化) 79 3.6 Shotgun Surgery(霰弹式修改) 80 3.7 Feature Envy(依恋情结) 80 3.8 Data Clumps(数据泥团) 81 3.9 Primitive Obsession(基本类型偏执) 81 3.10 Switch Statements(switch惊悚现身) 82 3.11 Parallel InheritanceHierarchies(平行继承体系) 83 3.12 Lazy Class(冗赘类) 83 3.13 Speculative Generality(夸夸其谈未来性) 83 3.14 Temporary Field(令人迷惑的暂时字段) 84 3.15 Message Chains(过度耦合的消息链) 84 3.16 Middle Man(中间人) 85 3.17 Inappropriate Intimacy(狎昵关系) 85 3.18 Alternative Classes with Different Interfaces(异曲同工的类) 85 3.19 Incomplete Library Class(不完美的库类) 86 3.20 Data Class(纯稚的数据类) 86 3.21 Refused Bequest(被拒绝的遗赠) 87 3.22 Comments(过多的注释) 87 第4章 构筑测试体系 89 4.1 自测试代码的价值 89 4.2 JUnit测试框架 91 4.3 添加更多测试 97 第5章 重构列表 103 5.1 重构的记录格式 103 5.2 寻找引用点 105 5.3 这些重构手法有多成熟 106 第6章 重新组织函数 109 6.1 Extract Method(提炼函数) 110 6.2 Inline Method(内联函数) 117 6.3 Inline Temp(内联临时变量) 119 6.4 Replace Temp with Query(以查询取代临时变量) 120 6.5 Introduce Explaining Variable(引入解释性变量) 124 6.6 Split Temporary Variable(分解临时变量) 128 6.7 Remove Assignments to Parameters(移除对参数的赋值) 131 6.8 Replace Method with Method Object(以函数对象取代函数) 135 6.9 Substitute Algorithm(替换算法) 139 第7章 在对象之间搬移特性 141 7.1 Move Method(搬移函数) 142 7.2 Move Field(搬移字段) 146 7.3 Extract Class(提炼类) 149 7.4 Inline Class(将类内联化) 154 7.5 Hide Delegate(隐藏“委托关系”) 157 7.6 Remove Middle Man(移除中间人) 160 7.7 Introduce Foreign Method(引入外加函数) 162 7.8 Introduce Local Extension(引入本地扩展) 164 第8章 重新组织数据 169 8.1 Self Encapsulate Field(自封装字段) 171 8.2 Replace Data Value with Object(以对象取代数据值) 175 8.3 Change Value to Reference(将值对象改为引用对象) 179 8.4 Change Reference to Value(将引用对象改为值对象) 183 8.5 Replace Array with Object(以对象取代数组) 186 8.6 Duplicate Observed Data(复制“被监视数据”) 189 8.7 Change Unidirectional Association to Bidirectional(将单向关联改为双向关联) 197 8.8 Change Bidirectional Association to Unidirectional(将双向关联改为单向关联) 200 8.9 Replace Magic Number with Symbolic Constant(以字面常量取代魔法数) 204 8.10 Encapsulate Field(封装字段) 206 8.11 Encapsulate Collection(封装集合) 208 8.12 Replace Record with Data Class(以数据类取代记录) 217 8.13 Replace Type Code with Class(以类取代类型码) 218 8.14 Replace Type Code with Subclasses(以子类取代类型码) 223 8.15 Replace Type Code with State/Strategy(以State/Strategy取代类型码) 227 8.16 Replace Subclass with Fields(以字段取代子类) 232 第9章 简化条件表达式 237 9.1 Decompose Conditional(分解条件表达式) 238 9.2 Consolidate Conditional Expression(合并条件表达式) 240 9.3 Consolidate Duplicate Conditional Fragments(合并重复的条件片段) 243 9.4 Remove Control Flag(移除控制标记) 245 9.5 Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件表达式) 250 9.6 Replace Conditional with Polymorphism(以多态取代条件表达式) 255 9.7 Introduce Null Object(引入Null对象) 260 9.8 Introduce Assertion(引入断言) 267 第10章 简化函数调用 271 10.1 Rename Method(函数改名) 273 10.2 Add Parameter(添加参数) 275 10.3 Remove Parameter(移除参数) 277 10.4 Separate Query from Modifier(将查询函数和修改函数分离) 279 10.5 Parameterize Method(令函数携带参数) 283 10.6 Replace Parameter with Explicit Methods(以明确函数取代参数) 285 10.7 Preserve Whole Object(保持对象完整) 288 10.8 Replace Parameter with Methods(以函数取代参数) 292 10.9 Introduce Parameter Object(引入参数对象) 295 10.10 Remove Setting Method(移除设值函数) 300 10.11 Hide Method(隐藏函数) 303 10.12 Replace Constructor with Factory Method(以工厂函数取代构造函数) 304 10.13 Encapsulate Downcast(封装向下转型) 308 10.14 Replace Error Code with Exception(以异常取代错误码) 310 10.15 Replace Exception with Test(以测试取代异常) 315 第11章 处理概括关系 319 11.1 Pull Up Field(字段上移) 320 11.2 Pull Up Method(函数上移) 322 11.3 Pull Up Constructor Body(构造函数本体上移) 325 11.4 Push Down Method(函数下移) 328 11.5 Push Down Field(字段下移) 329 11.6 Extract Subclass(提炼子类) 330 11.7 Extract Superclass(提炼超类) 336 11.8 Extract Interface(提炼接口) 341 11.9 Collapse Hierarchy(折叠继承体系) 344 11.10 Form Tem Plate Method(塑造模板函数) 345 11.11 Replace Inheritance with Delegation(以委托取代继承) 352 11.12 Replace Delegation with Inheritance(以继承取代委托) 355 第12章 大型重构 359 12.1 Tease Apart Inheritance(梳理并分解继承体系) 362 12.2 Convert Procedural Design to Objects(将过程化设计转化为对象设计) 368 12.3 Separate Domain from Presentation(将领域和表述/显示分离) 370 12.4 Extract Hierarchy(提炼继承体系) 375 第13章 重构,复用与现实 379 13.1 现实的检验 380 13.2 为什么开发者不愿意重构他们的程序 381 13.3 再论现实的检验 394 13.4 重构的资源和参考资料 394 13.5 从重构联想到软件复用和技术传播 395 13.6 小结 397 13.7 参考文献 397 第14章 重构工具 401 14.1 使用工具进行重构 401 14.2 重构工具的技术标准 403 14.3 重构工具的实用标准 405 14.4 小结 407 第15章 总结 409
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值