以下内容来自<<重构 改善既有代码的设计>>

一、什么是重构

所谓重构(Refactoring)是这样一个过程:在不改变代码外在行为的前提下,对代码做出修改以改进程序的内部结构。重构是一种经千锤百炼形成的有条不紊的程序整理方法,可以最大限度地减少整理过程中引入错误的几率。从本质上说,重构就是在代码写好之后改进它的设计。

二、什么样的代码需要重构

哪些代码需要重构呢?<<重构 改善既有代码的设计>>中列出了一个“坏味道条款”,当代码中出现了这些坏味道时,应该考虑重构。

重构手法

代码的坏味道

1.用多态代替价格条件逻辑代码

  经过前一阶段的重构,我们注意到switch语句:在Rental类中使用了Movie类的属性,这不是什么好主意,如果不得不使用,我们应该尽量在自己对象上使用自己的数据,而不应该过多的使用别人的数据,如:

public class Rental...
    public 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;
    }

  这暗示我们应该先把getCharge()移动到Movie中:

public class Movie ...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;}
}

  为了能够让程序正常运行,我们需要将租期长度作为参数传递进去。然而,租期长度又来自于Rental类,这里你就可能产生疑问了。既然switch语句会影响两个类中的数据,我们为什么要把getCharge()从Rental(此方法在Rental中表示:将影片类型传递到Rental对象)类中移到Movie(将租期长度传递到Movie对象)类中呢?因为本系统可能发生的变化是影片类型的改变,这种变化是不稳定的。我希望引起的连锁反应是最小的,所以选择在Movie中计算费用。相应的,应该改变Rental类:

public class Rental ...public double getCharge() {return movie.getCharge(daysRented);}

  移动了getCharge()方法后,我以同样的手法处理点数(积分)的计算,以保证把将会因影片类型改变而改变的代码都放入Movie类中。Rental类就由:

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

变成:

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

  相应的Movie类中新增加一方法:

public class Movie...public int getFrequentRenterPoints(int daysRented) {if(getPriceCode()==Movie.NEW_RELEASE && daysRented>1) return 2;elsereturn 1;}
}

2.终于说到继承了

  我们有不同种类型的影片,它们以不同的类型回答相同的问题(影片类型的不同,都是为了计算出租赁的费用)。所以,我们想到了可以建立3个子类,每个子类都有自己的计费方式,它们计算各自的费用,它们的关系如下(以继承机制表现不同影片):

  这样,我们就可以用多态取代switch语句了。但是遗憾的是,我们不能这么干,因为一部影片可以在自己的生命周期内修改自己的类型,一个对象却不能在生命周期类修改自己所属的类。但是,我们仍有解决的办法,那就是运用State模式(或译:状态模式),运用它以后,我们的类关系应该如下(运动State模式表现不同的影片):

  这里增加了一个中间层,就可以在Price对象进行继承动作了,我们可以按照我们的需求随时改变Price(价格)。

  如果你很熟悉设计模式,你可能会问:这是一个State还是一个strategy?答案取决于Price类究竟代表计费方式还是代表影片的某个状态。对于模式的选择反映出你对结构的想法,这里把它视为影片的某种状态。如果未来你觉得strategy能更好的说明你的意图,你可以再改造它以形成strategy。

  接下来,我将运用3个重构准则。首先运用了Replace Type Code with State/strategy,将与影片类型相互依赖的行为(Type Code bahavior)移动到State模式内。然后运用Move Method将switch语句移到了Price类中,最后运用Replace Conditional with Polymorphism去掉switch语句。

  首先我使用Replace Type Code with State/strategy。第一步将与类型相依赖的属性使用Self Encapsulate Field以确保任何时候都可以通过get和set方法获得这些属性。这样做是因为其它类中的很多代码都已经使用了这些属性,大多数方法也通过get方法来获取这些属性。当然,构造方法仍然可以直接使用属性值。

public class Movie ...private String title; //片名
    private int priceCode; //价格代号//getter and setter
}

  构造方法中我们可以用setter代替,如:

public class Movie ...private String title; //片名
    private int priceCode; //价格代号//getter and setter
    public Movie(String title, int priceCode) {this.title = title;setPriceCode(priceCode);}
}

  现在我加入新的类,在Price 类中提供抽象方法getPriceCode(与类型相依赖的行为),子类中来实现这个抽象方法:

public abstract class Price {abstract int getPriceCode();
}
public class ChildrensPrice extends Price {@Overrideint getPriceCode() {return Movie.CHILDRENS;}
}
public class NewReleasePrice extends Price {@Overrideint getPriceCode() {return Movie.NEW_RELEASE;}
}
public class RegularPrice extends Price {@Overrideint getPriceCode() {return Movie.REGULAR;}
}

  先在我要修改Movie的访问方法(get和set方法),下面是重构前的样子:

private int priceCode; //价格代号
    public int getPriceCode() {return priceCode;}public void setPriceCode(int priceCode) {this.priceCode = priceCode;}

  这意味着,我必须在Movie类中保存一个Price对象而不再是一个priceCode变量,此外我还要修改访问方法:

public class Movie ...private Price price;public int getPriceCode() {return priceCode;}public void setPriceCode(int arg) {switch(arg) { //各种影片的价格不同
        case REGULAR:price = new RegularPrice();break;case NEW_RELEASE:price = new NewReleasePrice();break;case Movie.CHILDRENS:price = new ChildrensPrice();break;default:throw new IllegalArgumentException("Incorrect Price Code");}
}

  第二步,我将对getCharge()运用Move Method,重构之前如下:

public class Movie ...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 ...public double getCharge(int daysRented) {return price.getCharge(daysRented);
}
public abstract class Price ...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;}
}

  第3步,我将运用Replace Conditional with Polymorphism,将switch的每一个分支用于一个子类的覆写方法,重构后如下:

public abstract class Price {abstract double getCharge(int daysRented);
}
public class RegularPrice extends Price {public double getCharge(int daysRented) {double result = 2;if(daysRented>2)result += (daysRented-2)*1.5;return result;}
}public class NewReleasePrice extends Price {@Overridepublic double getCharge(int daysRented) {return daysRented*3;}
}
public class ChildrensPrice extends Price {@Overridepublic double getCharge(int daysRented) {double result = 1.5;if(daysRented>3)result += (daysRented-3)*1.5;return result;}
}

  最后,在对象点数(积分)的计算采用同样的方法重构,这里不再累赘叙述。这里需要注意的是普通片和儿童片的积分点数是1,新片的积分点数是2,重构的方法看下面的UML图就容易理解了:

  引入State模式花费了我们不少功夫,这样做是否值得呢?如果我要修改与任何与价格有关的行为,增加一个新的价格标准,或者其它有关价格的行为,我都将很容易的对系统进行修改,而这个程序的其它部分并不知道我运用了State模式。由于目前程序中的行为太少,所以我们修改起来很容易,当在一个大的系统中,比如与价格相关的行为有十多个,修改的难度与这个相比将会有很大区别。

3.结语

  这是一个简单的实例,我希望它能够让你对重构有一点感觉。实例中我使用了几个重构准则:Extract Method,Move MethodReplace Conditional with Polymorpbism。所有这些重构行为的目的都是为了是责任分配更合理,代码维护更容易。它将与结构化的编程方式有很大区别,尽管很多人习惯后者。不过只要你一习惯重构后的风格,你就很难在回到过去了,因为结构化的编程风格已经不能满足你的需求了。

  这个实例给你上的最重要一课:重构的节奏,测试,小修改,测试,小修改,测试,小修改…这是这样的节奏让重构既快速又安全。如果你能跟上这个节奏,你现在应该对重构有一个基本了解了,后面我们将了解一点背景,原理和理论,当然只是一点点。

1、 重复代码(Duplicated Code)

2、  过长函数(Long Method)

3、  过大的类(Large Class)

4、  过长参数列(Long Parameter List)

5、  发散式变化(Divergent Change)

6、 散弹式修改(Shotgun Surgery)

7、 依恋情结(Feature Envy)

8、  数据泥团(Data Clumps)

9、  基本类型偏执(Primitive Obsession)

10、switch 惊悚现身(Switch Statements)

11、平行继承体系(Parallel Inheritance Hierarchies)

12、冗赘类(Lazy Class)

13、夸夸其谈未来性(Speculative Generality)

14、令人迷惑的临时字段(Temporary Field)

15、过度耦合的消息链 (Message Chains)

16、中间人 (Middle Man)

17、狎昵关系 (Inappropriate Intimacy)

《重构-改善既有代码的设计》读书笔记

坏味道

特征

情况及处理方式

目标

重复代码

1.重复的表达式
2.不同算法做相同的事
3.类似代码

同一个类的两个函数有相同表达式

重复代码提取为方法

相同表达式只在一个类的一个方法出现,供其他方法调用

兄弟类含有相同表达式

重复代码提取为方法
提升方法到父类

不相干类含有相同代码

提取为独立类供调用

过长函数

1.代码前面有注释
2.条件表达式
3.循环

 

提取方法

每个方法只做一件事,方法要定义完善、命名准确

过大的类

1.一个类中有太多实例变量
2.一个类中有太多代码

部分字段之间相关性高

相关的字段和方法提取为类

每个类负责一组具有内在的相互关联的任务

某些字段和方法只被某些实例用到

这些字段和方法移到子类中

过长参数列

1.参数列过长
2.参数列变化频繁

方法可以通过其他方式获取该参数

让参数接受者自行获取该参数

只需要传给函数足够的、让其可以从中获取自己需要的东西就行了

同一对象的若干属性作为参数

在不使依赖恶化的情况下,使用整个对象作为参数

被调用函数使用了另一个对象的很多属性

将方法移动到该对象中

某些数据缺乏归属对象

首先创建对象

发散式变化

一个类受多种变化的影响

类经常因为不同的原因在不同的方向上发生变化

将特定原因造成的所有变化提取为一个新类

针对某一外界变化的所有修改,只应发生在单一类中,而这个类中所有的内容都应反映此变化

散弹式修改

一种变化引发多个类的修改

某种变化需要在许多不同的类中做出小修改

把所有需要修改的代码放进同一个类中

针对某一外界变化的所有修改,只应发生在单一类中,而这个类中所有的内容都应反映此变化

依恋情结

一个函数使用其他类属性比使用自身类属性还要多

某个函数从另一个对象调用了几乎半打的取值函数

将依恋代码提取为单独方法,移动到另一对象

将数据和对数据的操作行为包装在一起

数据泥团

同时使用的相关数据并未以类的方式组织
1.两个类中相同的字段
2.许多函数中相同的参数

 

先将字段提取为类,再缩减函数签名中的参数

总是绑在一起的数据应该拥有属于它们自己的对象

基本类型偏执

过多使用基本类型

总是被放在一起的基本类型字段

提取类

将单独存在的数据值转换为对象

参数列中有基本类型

提取参数对象

数组中容纳了不同的对象,需要从数组中挑选数据

用对象取代数组

基本数据是类型码

使用类替换类型码

带条件表达式的类型码

使用继承类替换类型码

Switch语句

相同的switch、case语句散布于不同地方

根据类型码进行选择的switch

使用多态替代switch

避免到处做相同的修改

单一函数中有switch

使用显式的方法取代参数

平行继承体系

1.为某个类增加子类时,必须为另一个类增加子类
2.某个继承体系类名前缀和另一个继承体系类名前缀相同

 

一个继承体系中的实例引用另一个继承体系中的实例,然后迁移成员

避免到处做相同的修改

冗赘类

类无所事事

父类和子类无太大差别

将它们合为一体

 

某个类没有做太多事情

将这个类所有成员移到另一个类中,删除它

夸夸其谈未来性

 

某个抽象类没有太大作用

将父子类合并

 

不必要的委托

将这个类所有成员移到另一个类中,删除它

函数的某些参数未用上

移除参数

函数名称带有多余的抽象意味

重命名函数名

函数只被测试方法调用

连同测试代码一并删除

令人迷惑的暂时字段

1.某个实例字段仅为某种情况而设
2.某些实例字段仅为某个函数的复杂算法少传参数而设

 

提取单独的类,封装相关代码

 

过度耦合的消息链

一长串的getThis或临时变量

客户类通过一个委托类来取得另一个对象

隐藏委托

消除耦合

中间人

某个类接口有大量的函数都委托给其他类,过度使用委托

有一半的函数

移除中间人

 

少数几个函数

直接调用

中间人还有其他行为

让委托类继承受托类

狎昵关系

某个类需要了解另一个类的私有成员

子类过分了解超类

将继承改为委托,把子类从继承体系移出

封装

类之间双向关联

去掉不必要的关联

类之间有共同点

提取新类

异曲同工的类

两个函数做同一件事,但是签名不同

 

合并

 

不完美的类库

类库函数构造的不够好,又不能修改它们

想修改一两个函数

在调用类增加函数

 

想添加一大堆额外行为

使用子类或包装类

幼稚的数据类

某个类除了字段,就是字段访问器、设置器

 

1.用访问器取代public字段
2.恰当封装集合
3.移除不需要的设置器
4.搬移对访问器、设置器调用方法到此类
5.隐藏访问器、设置器

封装

被拒绝的馈赠

派生类仅使用了基类很少一部分成员函数

子类拒绝继承超类接口

使用委托替代继承

 

过多的注释

一段代码有着长长的注释

 

消除各种坏味道

 

18、异曲同工的类 (Alternative Classes with Different Interfaces)

19、不完美的库类 (Incomplete Library Class)
20、纯稚的数据类 (Data Class)

21、被拒绝的遗赠 (Refused Bequest)

22、过多的注释 (Comments)

转载于:https://www.cnblogs.com/baiduligang/p/4247358.html

重构 改善既有代码的设计:代码的坏相关推荐

  1. 期末作业代码网页设计代码——优分期大学生购物商城(7页) 大学生大学生购物商城作业模板下载 静态HTML购物商城网页制作下载_网页设计代码

    HTML5期末大作业:大学生购物商城网站设计--优分期大学生购物商城(7页) 大学生大学生购物商城作业模板下载 静态HTML购物商城网页制作下载_网页设计代码 常见网页设计作业题材有 个人. 美食. ...

  2. 期末作业代码网页设计代码 游戏网页网站设计——卡通的萌王游戏网页(13页) HTML+CSS+JavaScript

    HTML5期末大作业:游戏网页网站设计--卡通的萌王游戏网页(13页) HTML+CSS+JavaScript 常见网页设计作业题材有 个人. 美食. 公司. 学校. 旅游. 电商. 宠物. 电器. ...

  3. 期末作业代码网页设计代码——蔬菜水果商城(10页) HTML+CSS+JavaScript 学html网页制作期末大作业成品_网页设计期末作业

    HTML5期末大作业 文章目录 HTML5期末大作业 一.作品展示 二.文件目录 三.代码实现 一.作品展示 二.文件目录 三.代码实现 <!DOCTYPE html PUBLIC " ...

  4. 期末作业代码网页设计代码——网站商城后台通用模板(30页) 大学生后台模板网页作品商城网页设计作业模板 学生网页制作源代码下载

    HTML5期末大作业:商城后台网站设计--网站商城后台通用模板(30页) 大学生后台模板网页作品商城网页设计作业模板 学生网页制作源代码下载 常见网页设计作业题材有 个人. 美食. 公司. 学校. 旅 ...

  5. 《重构,改善既有代码的设计》读书笔记

    重构,绝对是写程序过程中最重要的事之一.在写程序之前我们不可能事先了解所有的需求,设计肯定会有考虑不周的地方,而且随着项目需求的修改,也有可能原来的设计已经被改得面目全非了.更何况,我们很少有机会从头 ...

  6. 重构-改善既有代码的设计:重构原则(二)

    1.什么是重构 重构(Refactoring):在不改变软件的功能和外部可见性的情况下,为了改善软件的结构,提高清晰性.可扩展性和可重用性而对软件进行的改造,对代码内部的结构进行优化. 2.为何重构 ...

  7. 《重构-改善既有代码设计》读书笔记-重构篇

    2019独角兽企业重金招聘Python工程师标准>>> 重构定义 名词 对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本.--<重 ...

  8. 重构—改善既有代码的设计

    概述 1.1 参考资料 <重构-改善既有代码的设计>读后总结 <重构改善既有代码的设计> 22种代码的坏味道,一句话概括 1.2 何谓重构 首先要说明的是:视上下文不同,重构的 ...

  9. PHP 杂谈《重构-改善既有代码的设计》之二 对象之间搬移特性

    思维导图 索引: Ø Move Method(搬移函数) Ø Move Field (搬移值域) Ø Extract Class (提炼类) Ø Inline Class (将类内联化,就是把当前的类 ...

  10. 『重构--改善既有代码的设计』读书笔记----序

    作为C++的程序员,我从大学就开始不间断的看书,看到如今上班,也始终坚持每天多多少少阅读技术文章,书看的很多,但很难有一本书,能让我去反复的翻阅.但唯独『重构--改善既有代码的设计』这本书让我重复看了 ...

最新文章

  1. 演技之神周润发背后的故事——评影帝周润发鲜为人知的封神之路
  2. C# Task的用法
  3. small用于不连续数组_用Small、If、Index、Indirect、Row、Char、Match实现一对多查找...
  4. NLP之词向量:利用word2vec对20类新闻文本数据集进行词向量训练、测试(某个单词的相关词汇)
  5. 【UVA1638】杆子的排列
  6. ubuntu install scite and 中文乱码解决
  7. 【Es】Es 选主流程
  8. 单通道GNSS模拟器行业调研报告 - 市场现状分析与发展前景预测(2021-2027年)
  9. NCFM识别-Googlenet
  10. 【Oracle】恢复临时表空间数据文件
  11. SpringBoot启动报jdbc连接池错误
  12. Spring事务(4):事务属性之7种传播行为
  13. 论文阅读_ICD编码_TLSTM
  14. 重磅福利 | 知乎上赞同数最高的1000个回答2019最新版
  15. matlab输入二项分布函数,MATLAB如何使用binopdf函数计算二项分布的概率
  16. 一个文字类RPG游戏框架(走过路过别错过)C++
  17. 迈向智能世界2030的九大技术挑战与研究方向
  18. cc++语言参考手册_C ++值类别快速参考:第2部分
  19. 海康威视工业相机MAC地址
  20. java 文本转语音开发

热门文章

  1. jquerymobile应用中session、cookie
  2. docker中启动关闭删除所有的容器命令
  3. Mysql 允许所有IP访问
  4. 3.9 对称三位素数
  5. 前端技术周刊 2018-09-10:Redux Mobx
  6. 思维导图分析http之http协议版本
  7. PHP中关于取模运算及符号
  8. 携手助力新型智慧城市建设和科技创新发展
  9. 函数表达书-读书笔记
  10. 20155229 实验一《Java开发环境的熟悉》实验报告