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

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

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

目录

    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等,还有翻译的侯捷和熊节先生,为我们带来这么精彩的一本书。谢谢。 免责声明:本书仅供个人学习研究之用,不得用于任何商业目的,不得以任何方式修改本作品,基于此产生的法律责任本人不承担任何连带责任。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小虚竹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值