《重构:改善既有代码的设计》-学习笔记一(+实战解析)

我不是个伟大的程序员;我只是个有着一些优秀习惯的好程序员而己

本人比较直接,不说虚的,直接上干货。

目录

Duplicated Code(重复的代码)

Long Method(过长函数)

Long Parameter List(过长参数列)

Large Class(过大类)

提前总结就是四招:

一、重复的代码提炼成函数

二、把过长的函数变小

三、参数列太长或变化太频繁,参数对象化

四、大招:类的代码行数太多,要考虑提炼子类。

第一招 重复的代码提炼成函数

===================

第一种情况是:同一个class内的两个函数含有相同表达式(expression)。

void printOwing(String _name) {

Enumeration e =_orders.elements();

double outstanding = 0.0;

// print banner

System.out.println (“**************************”);

System.out.println (“***** Customer Owes ******”);

System.out.println (“**************************”);

// calculate outstanding

while (e.hasMoreElements()) {

Order each = (Order) e.nextElement();

outstanding += each.getAmount();

}

//print details

System.out.println (“name:” + _name);

System.out.println (“amount” + outstanding);

}

实际上这三部分都可以提炼。

优化后的结果

void printOwing(String _name) {

printBanner();

double outstanding = getOutstanding();

printDetails(_name,outstanding);

}

void printBanner() {

// print banner

System.out.println (“**************************”);

System.out.println (“***** Customer Owes ******”);

System.out.println (“**************************”);

}

void printDetails (String _name,double outstanding) {

System.out.println (“name:” + _name);

System.out.println (“amount” + outstanding);

}

double getOutstanding() {

Enumeration e = _orders.elements();

double result = 0.0;

while (e.hasMoreElements()) {

Order each = (Order) e.nextElement();

result = each.getAmount();

}

return result;

}

第二种情况:两个subclasses有相同的表达式,或者是相似的表达式

优化的方法是:抽取相同的表达式(属性和方法),放在父类里,两个子类再去继承。

**********************************************************************************

如果是相似的表达式,好抽出共性的,则用模板函数设计模式来处理。

这里用到了JAVA的两个特性,继承和多态。

优化的思路1


1、在各个subclass 中分解目标函数,把有差异的部分变成入参,封装成一个模板函数。

2、把模板函数放到父类中。

3、子类根据需要输入不同的入参,得到需要的结果。

*********************************************************************************

如果是相似的表达式,差异的地方不好抽共性,则用模板函数设计模式来处理。

这里用到了JAVA的两个特性,继承和覆写(overrides)。

优化的思路2


1、在各个subclass 中分解目标函数,使分解后的各个函数要不完全相同,要不完全不同。

2、父类有一个主函数包含完全相同的函数和完全不同的函数:相同的函数,抽到父类中,不相同的函数在父类中定义一个函数。

3、子类继承父类,然后覆写完全不同的函数,再调用主函数可得到期望的结果。

第二招 把过长的函数变小

================

百分之九十九的场合里,要把函数变小,只需使用Extract Method(第一招)。找到函数中适合集在一起的部分,将它们提炼出来形成一个新函数。

如果函数内有大量的参数和临时变量,它们会对你的函数提炼形成阻碍。这时就要用Replace Temp with Query来消除这些临时变量

Replace Temp with Query(以查询取代临时变量)

优化思路


1、找出只被赋值一次的临时变量。

2、将该临时变量声明为final

3、编译:这可确保该临时变量的确只被赋值一次。

4、将临时变量等号右侧部分提炼到一个独立函数中;

5、首先将函数声明为private。日后你可能会发现有更多class需要使用 它,彼时你可再放松对它的保护。

6、编译,测试:确保提炼出来的函数无任何连带影响(副作用),结果不变;

7、把临时变量全替换成独立出来的函数;

以上,over!

例子:未优化代码

double getPrice() {

int basePrice = _quantity * _itemPrice;

double discountFactor;

if (basePrice > 1000) discountFactor = 0.95;

else discountFactor = 0.98;

return basePrice * discountFactor;

}

开始优化 1~3步骤

double getPrice() {

final int basePrice = _quantity * _itemPrice;

final double discountFactor;

if (basePrice > 1000) discountFactor = 0.95;

else discountFactor = 0.98;

return basePrice * discountFactor;

}

4~6步骤

double getPrice() {

final int basePrice = basePrice();

final double discountFactor;

if (basePrice > 1000) discountFactor = 0.95;

else discountFactor = 0.98;

return basePrice * discountFactor;

}

private int basePrice() {

return _quantity * _itemPrice;

}

7步骤

double getPrice() {

final double discountFactor;

if (basePrice() > 1000) discountFactor = 0.95;

else discountFactor = 0.98;

return basePrice() * discountFactor;

}

private int basePrice() {

return _quantity * _itemPrice;

}

搞定basePrice之后,再以类似办法提炼出一个discountFactor():

double getPrice() {

final double discountFactor = discountFactor();

return basePrice() * discountFactor;

}

private double discountFactor() {

if (basePrice() > 1000) return 0.95;

else return 0.98;

}

最后的效果

double getPrice() {

return basePrice() * discountFactor();

}

private double discountFactor() {

if (basePrice() > 1000) return 0.95;

else return 0.98;

}

private int basePrice() {

return _quantity * _itemPrice;

}

通过以上的优化,一个大函数,已经变成了多个小函数,重点是代码的可读性提高了,顺带的代码量变少。

第三招 参数对象化

=============

当你看到一个函数的入参有四,五个,甚至更多时,且好几个函数都使用这组入参,这时就要用参数对象化来优化代码。这些函数可能隶属同一个class,也可能隶属不同的classes 。这样一组参数就是所谓的Date Clump (数据泥团)」。这时用一个对象封装这些参数,再用对象取代它们。

优化思路


1、入参有四,五个,甚至更多时,就要着手优化;

2、用一个新的class封装入参,并把这些参数设置为private严格保护起来,写这些参数的get方法和set方法。

3、原函数的入参变成这个新的class对象,函数里的参数用class对象对应的属性替换。

4、编译测试;

5、将原先的参数全部去除之后,观察有无适当函数可以运用Move Method 搬移到参数对象之中。

例子:未优化的代码

@Autowired

private AddressService addressService;

public List inquireAddressListAccount( Integer pageNum ,Integer pageSize,String addressName,String mobile,String zipCode,String consignee){

return addressService.inquireAddressList(pageNum,pageSize,addressName,mobile,zipCode,consignee);

}

优化

@Autowired

private AddressService addressService;

public List inquireAddressListAccount( Integer pageNum ,Integer pageSize,InquireAddressListInput output){

return addressService.inquireAddressList(pageNum,pageSize,output);

}

public class InquireAddressListInput(){

private String addressName;

private String mobile;

private String zipCode;

private String consignee;

public String getConsignee() {

return consignee;

}

public void setConsignee(String consignee) {

this.consignee = consignee;

}

public String getMobile() {

return mobile;

}

public void setMobile(String mobile) {

this.mobile = mobile;

}

public String getZipCode() {

return zipCode;

}

public void setZipCode(String zipCode) {

this.zipCode = zipCode;

}

public String getAddressName() {

return addressName;

}

public void setAddressName(String addressName) {

this.addressName = addressName;

}

}

第四招 大招-提炼类和提炼子类

===============

如果想利用单一class做太多事情,其内往往就会出现太多instance变量。一旦如此,Duplicated Code也就接踵而至了。

Extract Class 是Extract Subclass 之外的另一种选择,两者之间的抉择其实就是委托(delegation)和继承(inheritance)之间的抉择。

情况一:某个class做了应该由两个classes做的事。(Extract Class

优化思路1


1、明确每个class所负的责任,该做什么事情;

2、建立一个新class,用以表现从旧class中分离出来的责任;

3、建立「从旧class访问新class」的连接关系;

4、每次搬移后,编译、
测试。

5、决定是否让新的class曝光。

例子:未优化的代码

class Person{

private String _name;

private String _officeAreaCode;

private String _officeNumber;

public String getName() {

return _name;

}

public String getTelephoneNumber() {

return (“(” + _officeAreaCode + ") " + _officeNumber);

}

String getOfficeAreaCode() {

return _officeAreaCode;

}

void setOfficeAreaCode(String arg) {

_officeAreaCode = arg;

}

String getOfficeNumber() {

return _officeNumber;

}

void setOfficeNumber(String arg) {

_officeNumber = arg;

}

}

优化1~2步骤

可以将「与电话号码相关」的行为分离到一个独立class中

class TelephoneNumber{

private String _number;

private String _areaCode;

public String getTelephoneNumber() {

return (“(” + _areaCode + ") " + _number);

}

String getAreaCode() {

return _areaCode;

}

void setAreaCode(String arg) {

_areaCode = arg;

}

String getNumber() {

return _number;

}

void setNumber(String arg) {

_number = arg;

}

}

优化3步骤

class Person…

private String _name;

private TelephoneNumber _officeTelephone = new TelephoneNumber();

public String getName() {

return _name;

}

public String getTelephoneNumber(){

return _officeTelephone.getTelephoneNumber();

}

TelephoneNumber getOfficeTelephone() {

return _officeTelephone;

}

情况二:class 中的某些特性(features)只被某些(而非全部)实体(instances)用到。Extract Subclass(提炼子类)

优化思路2


1、为source class 定义一个新的subclass

2、为这个新的subclass 提供构造函数。

简单的作法是:让subclass 构造函数接受与superclass 构造函数相同的参数,并通过super 调用superclass 构造函数;

3、找出调用superclass 构造函数的所有地点。如果它们需要的是新建的subclass , 令它们改而调用新构造函数。

如果subclass 构造函数需要的参数和superclass 构造函数的参数不同,可以使用Rename Method 修改其参数列。如果subclass 构造函数不需要superclass 构造函数的某些参数,可以使用Rename Method 将它们去除。

如果不再需要直接实体化(具现化,instantiated)superclass ,就将它声明为抽象类。

4、逐一使用Push Down MethodPush Down Field 将source class 的特性移到subclass 去。

5、每次下移之后,编译并测试。

例子:未优化代码

–用来决定当地修车厂的工作报价:

class JobItem …

public JobItem (int unitPrice, int quantity, boolean isLabor, Employee employee) {

_unitPrice = unitPrice;

_quantity = quantity;

_isLabor = isLabor;

_employee = employee;

}

public int getTotalPrice() {

return getUnitPrice() * _quantity;

}

public int getUnitPrice(){

return (_isLabor) ?

_employee.getRate():

_unitPrice;

}

public int getQuantity(){

return _quantity;

}

public Employee getEmployee() {

return _employee;

}

private int _unitPrice;

private int _quantity;

private Employee _employee;

private boolean _isLabor;

class Employee…

public Employee (int rate) {

_rate = rate;

}

public int getRate() {

return _rate;

}

private int _rate;

优化1步骤

class LaborItem extends JobItem {}

优化2步骤

class LaborItem extends JobItem {

public LaborItem (int unitPrice, int quantity, boolean isLabor, Employee employee) {

super (unitPrice, quantity, isLabor, employee);

}

}

这就足以让新的subclass 通过编译了。但是这个构造函数会造成混淆:某些参数是LaborItem 所需要的,另一些不是。稍后我再来解决这个问题。

优化3步骤

清理构造函数参数列

class JobItem…

protected JobItem (int unitPrice, int quantity, boolean isLabor, Employee employee) {

_unitPrice = unitPrice;

_quantity = quantity;

_isLabor = isLabor;

_employee = employee;

}

public JobItem (int unitPrice, int quantity) {

this (unitPrice, quantity, false, null)

}

外部调用应该使用新构造函数:

JobItem j2 = new JobItem (10, 15);

测试通过后,再使用Rename Method 修改subclass 构造函数:

class LaborItem

public LaborItem (int quantity, Employee employee) {

super (0, quantity, true, employee);

}

可以将JobItem 的特性向下搬移。先从函数幵始,我先运用 Push Down Method 对付getEmployee() 函数:

class LaborItem extends JobItem {

public LaborItem (int unitPrice, int quantity, boolean isLabor, Employee employee) {

super (unitPrice, quantity, isLabor, employee);

}

public LaborItem (int quantity, Employee employee) {

super (0, quantity, true, employee);

}

public Employee getEmployee() {

return _employee;

}

}

//因为_employee 值域也将在稍后被下移到LaborItem ,所以我现在先将它声明为protected。

class JobItem…

protected Employee _employee;

将_employee 值域声明protected 之后,我可以再次清理构造函数,让_employee 只在「即将去达的subclass 中」被初始化:

class JobItem…

protected Employee _employee;

protected JobItem (int unitPrice, int quantity, boolean isLabor) {

_unitPrice = unitPrice;

_quantity = quantity;

_isLabor = isLabor;

}

class LaborItem …

public LaborItem (int quantity, Employee employee) {

super (0, quantity, true);

_employee = employee;

}

下一个优化_isLabor 值域,_isLabor 在JobItem是值为false,在LaborItem值为true。

可以用多态常量函数。所谓「多态常量函数」会在不同的subclass 实现版本中返回不同的固定值

class JobItem…

protected boolean isLabor() {

return false;

}

class LaborItem…

protected boolean isLabor() {

return true;

}

就可以摆脱_isLabor 值域了

通过多态代替条件的方式,重构代码

class JobItem …

public int getUnitPrice(){

return (isLabor()) ?

_employee.getRate():

_unitPrice;

}

将它重构为:

class JobItem…

public int getUnitPrice(){

return _unitPrice;

}

class LaborItem…

public int getUnitPrice(){

return _employee.getRate();

}

使用某项值域的函数全被下移至subclass 后,我就可以使用 Push Down Field 将值域也下移。

最后的结果就是:

public class JobItem {

protected JobItem (int unitPrice, int quantity) {

_unitPrice = unitPrice;

_quantity = quantity;

}

public int getTotalPrice() {

return getUnitPrice() * _quantity;

}

public int getUnitPrice(){

return _unitPrice;

}

public int getQuantity(){

return _quantity;

}

private int _unitPrice;

private int _quantity;

}

//

public class LaborItem extends JobItem {

private Employee _employee;

public LaborItem(int quantity, Employee employee) {

super(0, quantity);

_employee = employee;

}

public Employee getEmployee() {

return _employee;

}

public int getUnitPrice() {

return _employee.getRate();

}

}

//public class Employee {

public Employee(int rate) {

_rate = rate;

}

public int getRate() {

return _rate;

}

private int _rate;

}

一个class如果拥有太多代码,也适合使用Extract ClassExtract Subclass

想重构代码,直接把以上四招看情况用上,更多精彩内容,请等待后续更新。

***************************************************************************
作者:小虚竹
欢迎任何形式的转载,但请务必注明出处。
限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。
我不是个伟大的程序员,我只是个有着一些优秀习惯的好程序员而己

直很喜欢重构这本书,但是由于自己记性不太好,书看过之后其中的方法总是记不住,于是想如果有电子版的重构书就好了,工作中遇到重构的问题可以随时打开查阅。在网上搜索了许久,发现重构这本书有英文chm版本的,而中文版的电子书只有扫描的PDF版本,用起来非常不方便。于是萌生想做重构工具书的想法,本来打算自己重新将重构书的内容再整理归类下,后来发现原书的目录编排就很适合做工具书,包括坏味道分类,重构手法归类等,都有了个比较系统的整理。因此,我利用空余时间制作了这样的本中文的chm版重构,希望对大家有所帮助,也算对中国软件业做出点小小的贡献。 本书基本上是取自”重构”中文版书的内容,但格式上参照的是chm英文版的格式,还有些格式小修改,比如第章的重构前后代码对比。因为时间匆促,个人能力有限,本书难免存在些缺漏,如果大家发现有问题,随时可以给我发邮件,我会尽快更新错误的内容。 最后再次感谢几位大师 Martin Fowler、Kent Beck等,还有翻译的侯捷和熊节先生,为我们带来这么精彩的本书。谢谢。 免责声明:本书仅供个人学习研究之用,不得用于任何商业目的,不得以任何方式修改本作品,基于此产生的法律责任本人不承担任何连带责任。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值