小结_01:《重构:改善既有代码的设计》

来源:互联网 发布:两台nginx做负载均衡 编辑:程序博客网 时间:2024/05/16 13:02

重构的意义: 你永远不必说对不起————只要把出问题的地方修补好就行了。


以下小结各种重构技巧

 Part I : 重构技术的基础之一:在对象之间搬移特性

( 涉及概念: Method; Field; Class; Delegate; Middle Man; Foreign Method; Local Extension)

一.   搬移方法:

      实施的条件:   如果一个类有很多方法,或者一个类与另一个类有太多合作而形成高度耦合;那么考虑搬移方法
       (代码坏味道:  使用另一个对象的次数比使用自己所驻对象的次数还多。)
     实施结果: 系统变得更加的清晰。
     操作的注意事项:
        1. 在将目标函数从源类中移到目标类中去了之后,可以直接去掉源函数,或者将其变成“委托函数”,(如下就是一个委托函数):
          

class Account...            double getFee(){                return _transaction.getFee(_daysOfRent);            }        


        2.如果移除了源函数的话,在强类型的语言中,编译器会帮助我发现任何“调用源函数”方面的遗漏。
        3.如果被搬移的函数使用了多个源类中的字段,那么应该将源类的对象作为参数传递给这个函数的调用;
            但是,如果这些字段过多的话,理应在“搬移方法”之前就将目标函数分解,将其中的一部分保留在源类当中。


二.  搬移字段:
          实施的条件:对于一个字段,在其所驻类之外的另一个类中有更多函数使用了它,就应该考虑搬移这个字段。
         操作注意事项:
               1. 如果字段的访问级别是public级,就应该把它“自封装”起来:
                   (意思就是,对这个字段设计set和get方法)
                   或者,即使该字段的级别是private级别的,但是有多个函数在访问它,那么也应该“自封装”


三.  对Class的处理:

     3.1 提取新类:

            类是不断地会被修改和变化的;
            如果某些数据和某些函数总是一起出现,某些数据经常同时变化甚至彼此相依,就是应该将它们分离出去的时候了。
            如果类中有两部分或多部分特性需要不同的子类化策略,那么也需要提取新类;
            注意事项:
                 a. 必须考虑需不需要向外公布新类;
                             如果不公布,那么新类中的函数将全部被委托至旧类;
                             如果公布, 而且允许任何人修改属性,那么必须使得新类的对象成为“引用对象”(即用工厂方法控制该类对象的创建)  

      3.2  内联类:

            如果一个类没有做太多的事情, 那么需要“将这个类搬移到其他的类”,即使之内联化:
            操作注意事项:
                 a. 先要在目标类中建立对所有源类public函数的“委托”;再修改所有对源类函数的调用点;最后放心大胆地把源类特性全部搬到目标类中。        

      3.3  委托关系:

             尽量隐藏委托关系;(不希望客户深入了解是如何委托的,只需要调用接口中的函数就行了)
             做法: 首先对于服务模块中的每一个委托关系中的函数,都在服务对象端(Client端)建立一个简单的委托函数。
   
             但是,如果某个类里面全是委托关系,这个服务类变成了一个“中间人”,那么可以让客户直接调用受委托类(即“移除中间人类”)

      3.4  Local Extension:

              如果需要为服务类提供一些额外函数,但是无法修改这个类的时候,需要建立一个子类来扩展这个类,把这些函数添加到子类当中去。


Part II: 其他技巧

一:重构当中最具观赏性的工作: “以多态取代条件表达式”:

        现象: 如果case 条件中的字段分属于不同的子类对象,那么就可以使用多态了
        操作如下:
               1. 在子类中建立同样的函数,分别执行case中设定的逻辑;
               2. 为这些子类的超类(如果没有就建立一个)构造一个同名抽象函数;
               3. 在调用端使用委托的方式调用超类中的抽象函数。

       与条件表达式相关的技术还有:“以卫语句取代条件表达式”:
       动机:函数中的条件逻辑使人难以看清正常的执行路径
       但是,条件表达式通常有两种表现形式, 第一:所有分支都属于正常行为。 第二:条件表达式提供的答案中只有一种是正常行为,其他的都是不常见的行为。
                   对于第二种情况,就应该单独检查该条件,并在该条件为真时立即从函数中返回。这样的单独检查常常被称为“卫语句”(guard clause),效果如下:
 

double getPayAmout(){    if(_isDead) return deadAmout();    if(_isSepatated) return separatedAmount();    if(_isRetired) return retiredAmout();    return normalPayAmount();  }

二: 优化函数

         a)将查询函数和修改函数分离:
               优化函数有一条好规则就是:任何有返回值的函数,都不应该有看得到的副作用
               如果一个函数修改了值又返回,那么在其他的地方调用它就会不那么放心;
               注意事项:在多线程编程中,经常需要在同一个动作中完成查询和赋值;
               如果要追求更好的函数表现,我会将查询和赋值分开来,同时使用一个synchronized修饰的函数取代原来的动作并调用这两个“查询”和“赋值”的函数。

          b)以工厂函数取代构造函数:
                使用这个方法的动机在于,避免根据类型码来创建相应的对象,即避免将子类名称暴露给用户。
                操作步骤:
                 1. 新建一个工场函数,使用工厂函数调用现有的构造函数
                 2. 将调用构造函数的代码换为调用工厂函数。
                 3. 将构造函数声明为private;
                接下来一般还要继续做的是“将类型码替换为子类”, 在新的工场函数里面完成对不同子类对象的创建;这一般会产生一个switch语句。
                如果要绕过这种switch语句,一个好办法就是使用Class.forName()。比如下面一段代码:
     

static Employee create(int type){            switch(type){                case ENGINEER:                    return new Engineer();                case SALESMAN:                    return new Salesman();                case MANAGER:                    return new Manager();                default:                    throw new IllegalArgumentException("Incorrect type code value");            }      }


                 经过一系列操作以后,可以变化为如下形式:
     
static Employee create(String name){            try{                return (Employee) Class.forName(name).newInstance();            } catch(Exception e) {                throw new IllegalArgumentException("Unable to instantiate" + name);            }      }


原创粉丝点击