重构接口

由来

在实现类很多的情况下改变接口方法的签名(参数)是一件复杂的高风险的工作。因为接口方法的改变代表着所有相关的实现类也要随之而改变。 本文以一个实际的重构为案例探讨了如何让重构能够可分步验证的,迭代的进行,如何减少复杂的重构所带来的风险,并保证重构的质量。

案例背景:

1.改变接口签名(参数)的重构

比如 原有接口方法 如下:
public interface Decorator
{
    void process(ClassA objectA,ClassB objectB);
}

重构的任务是改变这个接口方法的参数,新的接口方法如下
public interface Decorator
{
    void process(ClassA objectA,ClassC objectC)
}

2.原有接口方法已有10个以上的实现。

3.原有接口方法的所有实现已经有相应的单元测试。

任何重构对原有代码是否已有完备的单元测试有很强的依赖性,因为:
3.1 如果老代码没有配备比较完善的单元测试代码,重构的结局可能很惨,漏洞百出,系统在重构后变得不稳定。还有一种可能的情况是开发人员不愿意也不敢重构。
3.2 如果代码本身配备有测试代码,那么重构代码会变的轻松很多,而且重构出新bug的风险性也会小很多。

4. 重构的目的

 不是增加新的业务逻辑,而是想改变代码结构,进一步降低关键业务对象之间的耦合性。从实现上来说,新接口方法的新参数ClassC 的数据来自于ClassB。重构的目的是 想让ClassC 与原有接口方法process解耦合。即把ClassC转换为ClassB的逻辑抽离出来,放到上一层的调用函数中。

方案:

1.方案1:

1.1 方案描述

  比较普遍的做法是直接改变这个接口方法,所有的实现都会报错。然后逐一的让这些编译错误消失。

1.2 方案缺点

这种重构带来的问题是必须一次改完所有的实现才能提交代码,不利于任务的划分和迭代开发。

2.方案2:

2.1方案描述

  相对好的的做法是新增加一个接口方法,让新老接口方法并存,在逐步实现并测试完新的接口方法后再代替原来的接口。

2.2 方案优点

 这样做的好处是可以分次提交代码,再逐一测试过每一个新的实现后再用新的接口来替代老接口。

2.3 方案缺点

   没有充分利用老的单元测试代码。

3.方案3

3.1 方案描述

 方案2的优点很明显,但是如何利用老测试代码呢?
 我们在重构背景中描述过本案例的目的,即不改变原有逻辑的前提下把原有的实现抽离一部分逻辑出来提到上一层调用函数中去。
 原有的代码如下:
process(ClassA objectA,ClassB objectB){
  //part1:从objectB初始化objectC  
   ......

  //part2:根据objectC 做业务处理
   ......
}

part2代码是要移到新的process方法中的,而part1的代码是要移到调用process的函数中去的。
那么,根据以上情况,重构方案3,可以分如下步骤进行
  1>在接口类中增加新的接口方法
    public interface Decorator
    {
      void process(ClassA objectA,ClassB objectB);
      void process(ClassA objectA,ClassC objectC);// 新接口方法
    }
  2> 在其中一个实现类中增加新的接口的实现(包含原方法part2的代码)

         process(ClassA objectA,ClassC objectC){
        //part2:根据objectC 做业务处理
           ......
        }
  3> 修改老的process方法(删除原来的part2的代码,并调用新的接口实现)
     public class Decorator1: Decorator{
       process(ClassA objectA,ClassB objectB){
         //part1:从objectB初始化objectC  
         ......
         process(objectA,objectC);
       }            
     }
  4> 运行Decorator1现有的的单元测试代码
     如果单元测试不能通过,说明重构的代码有问题。在修改bug并保证测试能通过之后,提交代码。

  5> 重复2-4步,保证重构完的所有单元测试都通过。
  6> 把原来对老process的单元测试,逐一改为对新的process方法的测试
  7> 删除老的process方法,重构完成。

#### 3.2 方案的优点
  1>保持了方案2的优点,任务可以划分得很细。
    可以根据情况决定完成和提交多少代码。而不是等到所有重构完成才能提交代码,有利于团队开发。
  2>充分利于了原有单元测试代码
    不是直接重构并且修改测试代码(同时要关心2件事情),而是先利用原有测试代码去验证新的接口实现,然后在保证新的接口实现是正确的前提下再把老的测试代码改为新的测试代码。拆分为2个步骤,每个步骤开发者只需要关心一件事情,能够提高开发效率和代码的质量。
  3>减轻架构师的工作
    由于以上2个优点(任务可拆分并可分步验证),使这项复杂工作交给普通的程序员来做成为可能。

实际案例

下面以一个实际的架构重构的一部分作为案例详细描述下重构的流程。

###首先介绍下案例用到的的基础知识 1.适配器模式: 适配器模式主要用于不改变原有类的情况下,改变其功能。 适配器模式的组成 适配器模式包括2种类,装饰类(装饰抽象类和其实现类)和被装饰类(被装饰类接口和其实现类) 如下图

装饰器模式 其中装饰类也实现被装饰类的接口,同时又包含一个被装饰类的引用。并在 这样具体的调用中,初始化一个装饰类并注入已被装饰的类的实例到该装饰类。

1.2 C# 的Attribute 1.3 C#的类反射

2.适配器模式在架构中应用 案例的架构背景 3.重构开始

( 本文版权属于© 2015 卓逸天成 | 转载请注明作者和出处:卓逸知识文库 http://kb.skight.com )