第三章  代码的坏味道

 

3.1 Duplicated Code重复代码

3.1.1

 

 最单纯的Duplicated Code就是“同一个类的两个函数含有相同的表达式”,这时你需要做的就是采用Extract Method(110)提炼重复的代码,然后让这两个地点都调用被提炼出来的那一段代码

 

Extract Method(提炼函数)

你有一段代码可以被组织在一起并独立出来

将这段放进一个独立的函数中,并让函数名称解释该函数的用途

1

       public void printOwing(double amount){

              printBanner();

             

             

              System.out.println("name:"+_name);

              System.out.println("amount:"+amount);

       }

      

      

2    

       public void printOwing(double amount){

              printBanner();

              printDetails(amount);

       }

      

       public void printDetails(double amount){

              System.out.println("name:"+_name);

              System.out.println("amount:"+amount);

       }

}

 

做法:

1.创造一个新函数,根据这个函数的意图来对它命名(以它做什么来命名,而不是它怎么做命名)

2.将提炼出的函数的代码从原函数复制到新建的目标函数中

3.仔细检查提炼出的代码,看看其中是否引用了“作用域限于原函数”的变量

(包括局部变量和原函数参数)

4.检查是否有“仅用于被提炼代码段”的临时变量。如果有,在目标函数中将它们声明为临时变量

5.检查被提炼的代码段,看看是否有任何局限变量的值被它改变。

如果一个临时变量值被修改了,看看是否可以将被提炼代码段处理为一个查询,

并将结果赋值给相关变量。如果很难这样做,或者如果被修改的变量不止一个,你就不能仅仅将这段代码原封不动地提炼出来。你可能需要先使用Split Temportaary Variable(128) ,然后再尝试提炼。也可以使用Replace Temp with Query120

将临时变量消灭掉

6.将被提炼代码段中需要读取的局部变量,当作参数传递给目标函数

7.处理完所有局部变量之后,进行编译

8.在原函数中,将被提炼代码段替换为对目标函数的调用

 

范例:无局部变量

public void printOwing(){

          

           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);

      }

提炼代码:

      public void printOwing(){

          

           Enumeration e = _orders.elements();

           double outstanding = 0.0;

          

           //print banner

           printBanner();

          

           //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);

      }

      private void printBanner() {

           System.out.println("********************************");

           System.out.println("*******Customer owes********");

           System.out.println("********************************");          

      }

 

 

范例:有局部变量

 

局部变量最简单的情况是:被提炼代码段只是读取这些变量的值,

并不修改它们,这种情况下我们可以简单地将它们当作参数传递给目标函数

      public void printOwing(){

          

           Enumeration e = _orders.elements();

           double outstanding = 0.0;

          

           //print banner

           printBanner();

          

           //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);

      }

      private void printBanner() {

           System.out.println("********************************");

           System.out.println("*******Customer owes********");

           System.out.println("********************************");          

      }

}

 

 

public void printOwing(){

          

           Enumeration e = _orders.elements();

           double outstanding = 0.0;

          

           //print banner

           printBanner();

          

           //calculate outstanding

           while(e.hasMoreElements()){

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

                 outstanding += each.getAmount();

           }

          

           //print details

           printDetails(outstanding);

      }

      public void printDetails(double amount){

           System.out.println("name:"+_name);

           System.out.println("amount:"+amount);

      }

      private void printBanner() {

           System.out.println("********************************");

           System.out.println("*******Customer owes********");

           System.out.println("********************************");          

      }

}

 

 

 

 

 

范例:对局部变量再赋值

被赋值的临时变量也分两种情况,较简单的情况是:

这个变量只是在被提炼的代码段中使用。果真如此,你可以将这个临时变量的声明移到被提炼代码段中,然后一起提炼出去。

 

另一种情况是:被提炼代码段之外的代码也使用了这个变量。这又分为两种情况:

1. 如果这个变量在被提炼代码段之后未在被使用,

你只需直接在目标函数中修改它就可以了。

2. 如果被提炼的代码之后的代码还使用了这个变量,

你就需要让目标函数返回该变量改变后的值

 

public void printOwing(){

          

           Enumeration e = _orders.elements();

           double outstanding = 0.0;

          

           //print banner

           printBanner();

          

           //calculate outstanding

           while(e.hasMoreElements()){

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

                 outstanding += each.getAmount();

           }

          

           //print details

           printDetails(outstanding);

      }

      public void printDetails(double amount){

           System.out.println("name:"+_name);

           System.out.println("amount:"+amount);

      }

      private void printBanner() {

           System.out.println("********************************");

           System.out.println("*******Customer owes********");

           System.out.println("********************************");          

      }

 

 

提炼代码:

      public void printOwing(){

          

           //print banner

           printBanner();

     

           double outstanding = getOutStanding();

           //print details

           printDetails(outstanding);

      }

      public double getOutStanding(){|

           Enumeration  e  = _orders.elements();

           double outstanding  = 0.0;

           while(e.hasMoreElements()){

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

                 outstanding += each.getAmount();

           }

           return outstanding;

      }

 

Enumerration变量e只是在被提炼代码段中用到。

所以可以将它整个搬到新函数中。Double变量outStanding在被提炼代码段中内外都被用到,所以必须让提炼代码出来的新函数返回它

     

      public void printOwing(double previousAmount){

          

           double outstanding  = previousAmount * 12;

           //print banner

           printBanner();

     

           double outstanding = getOutStanding(previousAmount);

           //print details

           printDetails(outstanding);

      }

       

     

      public double getOutStanding(double initialValue){

           Enumeration  e  = _orders.elements();

           double outstanding  = initialValue;

           while(e.hasMoreElements()){

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

                 outstanding += each.getAmount();

           }

           return outstanding;

      }

 

Split Temporary Variable(分解临时变量)

你的程序有某个临时变量被赋值超过一次,它既不是循环变量也不被用于收集计算结果

针对每次赋值,创造一个独立,对应的临时变量

 

1

double temp = 2 *( _height + _width);

           System.out.println(temp);

           temp = _height * _width;

           System.out.println(temp);

          

      2   

           final double perimeter = 2 * (_height  + _width);

           System.out.println(perimeter);

           final double area = _height * _width;

           System.out.println(area);

 

 

做法:

  1. 在待分解临时变量的声明及其第一次被赋值处,修改其名称
  2. 将新的临时变量声明为final
  3. 以该临时变量的第二次赋值动作为界,修改此前对该临时变量的所有引用点

让它们引用新的临时变量

4.在第二次赋值处,重新声明原先那个临时变量

5.编译测试

6、逐次重复上述过程,每次都在声明处对临时变量改名,并修改下次赋值的引用点

 

范例“:

1

      double getDistanceTravelled(int time){

           double result;

           double acc = _primaryForce /_mass;

           int primaryTime = Math.min(time,_delay);

           result = 0.5 * acc * primaryTime * primaryTime;

           int secondaryTime = time - _delay;

           if(secondryTime > 0){

                 double  primaryVel = acc * _delay;

                 acc = (_primaryForce + _secondaryForce ) /_mass;

                 result += primaryVel * secondaryTime +0.5 *acc *secondaryTime *secondaryTime;

           }

           return result;

          

}

第一个变量保存力造成的初始加速度,第二个是保存两个力共同造成的加速度,这就是我想要分解的东西

 

 

  1.  将新的临时变量声明为final,以该临时变量的第二次赋值动作为界,修改此前对该临时变量的所有引用点

让它们引用新的临时变量,在第二次赋值处,重新声明原先那个临时变量、

新的临时变量名称指出,它只承担原先Acc变量的第一个责任,我将它声明为final,确保它只被赋值一次

 

      double getDistanceTravelled(int time){

           double result;

           final double primaryAcc = _primaryForce /_mass;

           int primaryTime = Math.min(time,_delay);

           result = 0.5 * primaryAcc * primaryTime * primaryTime;

           int secondaryTime = time - _delay;

           if(secondryTime > 0){

                 double  primaryVel = primaryAcc * _delay;

                 double acc = (_primaryForce + _secondaryForce ) /_mass;

                 result += primaryVel * secondaryTime +0.5 *acc *secondaryTime *secondaryTime;

           }

           return result;

          

      }

 

 

3、

double getDistanceTravelled(int time){

           double result;

           final double primaryAcc = _primaryForce /_mass;

           int primaryTime = Math.min(time,_delay);

           result = 0.5 * primaryAcc * primaryTime * primaryTime;

           int secondaryTime = time - _delay;

           if(secondryTime > 0){

                 double  primaryVel = primaryAcc * _delay;

                 final double secondaryAcc = (_primaryForce + _secondaryForce ) /_mass;

                 result += primaryVel * secondaryTime +0.5 *secondaryAcc *secondaryTime *secondaryTime;

           }

           return result;

          

      }

3.1.2

另一种常见的情况是“两个互为兄弟的子类含有相同的表达式”

只需对两个类都使用Extract Method(110).然后再对被提炼出来的代码使用Pull up Method322),将它推入超类内。如果代码之间只有类似,并非完全相同,那么就得运用Extract Method将相似部分和差异部分分隔开来,构造单独一个函数。然后你可能发现这些可以运用Form Template Method345)获得一个Tempalte Method设计模式

如果有些函数以不同的算法做相同的事,你可以选择其中较清晰的一个,并使用Subsitute Algorithm139)将其他函数的算法替换掉。

 

 

 

Pull up method(函数上移)

有些函数,在各个子类中产生完全相同的结果。

将该函数移至超类

 

做法:

1、         检查待提升函数,确定它们是否完全一致。

2、         如果待提升函数的签名不同,将那些签名都修改为你想要在超类中使用的签名

3、         在超类中新建一个函数,将某一个待提升函数的代码复制到其中,做适当调整,然后编译

  1. 3.     移除待提升的子类函数
  2. 4.     编译,测试
  3. 5.     逐一移除待提升的子类函数,直至只剩下超类中的函数为止。

每次移除之后都需要测试。

  1. 6.     观察该函数的调用者,看看是否可以改为超类类型的对象

 

 

 

范例:

我以Customer表示顾客,他有两个子类,

表示普通顾客和贵宾

 

 

 

 

 

 

      void createBill(date date){

           double chargeAmount = chargeFor(lastBillDate,date);

           addBill(date,charge);

      }

      class Customer{

           abstract double chargeFor(date start,date end);

      }

     

 

Form Template Method(塑造模版函数)

你有一些子类,其中相应的某些函数以相同的顺序执行类似的操作,

但各个细节上有所不同。

 

将这些操作分别放进独立函数中,并保持它们都有相同的签名,

于是原函数也就变得相同了,然后将原函数上移至超类

 

 

 

 

做法:

  1. 1.     在各个子类中分解目标函数Extract method,使分解后的各个函数要不完全相同,要不完全不同。
  2. 2.     运用pull up method322)将各个子类完全相同的函数上移至超类
  3. 3.     对于那些(剩余的,存在与各个子类内的)完全不同的函数,实施rename method,使所有这些函数的签名完全相同。
  4. 4.     修改上述所有签名后,编译并测试
  5. 5.     运用pull up method将所有原函数逐一上移至超类中,

在超类中将那些代表各种不同操作的函数定义为抽象函数

  1. 6.     编译测试
  2. 7.     移除其他子类中的原函数,每删除一个,编译测试

 

 

范例:

1.
//用于打印ASCII玛报表

      public void statement(){

           Enumeration rentals = _rentals.elements();

           String result = "Rental Record for "+getName()+"\n";

           while(rentals.hasMoreElements()){

                 Rental rental = (Rental)rentals.nextElement();

                

                 result += "\t"+each.getMovie().getTitle()+"\t"+String.valueOf(each.getCharge())+" \n";

           }

          

           result += "Amount owed is "+String .valueOf(getTotalCharge()+"\n");

           result +="you earned "+String.valueof(getTotalFrequencyRentalPoints())+" frequnecty rentar points";

           return result;

      }

 

//用于打印HTML报表

           public void htmlstatement(){

                 Enumeration rentals = _rentals.elements();

                 String result = "<H1>Rental Record for <EM>"+getName()+"</EM></H1><P>\n";

                 while(rentals.hasMoreElements()){

                      Rental rental = (Rental)rentals.nextElement();

                     

                      result += "\t"+each.getMovie().getTitle()+"\t"+String.valueOf(each.getCharge())+" <BR>\n";

                 }

                

                 result += "<P>Amount owed is <EM>"+String .valueOf(getTotalCharge()+"\n</EM></p>");

                 result +="<Em>you earned "+String.valueof(getTotalFrequencyRentalPoints())+" frequnecty rentar points</em>";

                 return result;

           }

 

2.使它们称为同一超类中的子类

class Statement(){}

class TextStatement() extends Statement{}

class HtmlStatement() extends Statement{}

 

3.通过move method将打印移动到对应子类

class Customer{

                 public String statement(){

                      return  new  TextSatement().value(this);

                 }

                 public String htmlstatement(){

                      return new HtmlStatement().value(this);

                 }

           }

           class TextStatement{

                 public void value(Customer customer){

                      Enumeration rentals = _rentals.elements();

                      String result = "Rental Record for "+getName()+"\n";

                      while(rentals.hasMoreElements()){

                            Rental rental = (Rental)rentals.nextElement();

                           

                            result += "\t"+each.getMovie().getTitle()+"\t"+String.valueOf(each.getCharge())+" \n";

                      }

                     

                      result += "Amount owed is "+String .valueOf(getTotalCharge()+"\n");

                      result +="you earned "+String.valueof(getTotalFrequencyRentalPoints())+" frequnecty rentar points";

                      return result;

                 }

           }

          

           class htmlStatement{

                 public void value(Customer customer){

                      Enumeration rentals = _rentals.elements();

                       String result = "<H1>Rental Record for <EM>"+getName()+"</EM></H1><P>\n";

                      while(rentals.hasMoreElements()){

                            Rental rental = (Rental)rentals.nextElement();

                           

                            result += "\t"+each.getMovie().getTitle()+"\t"+String.valueOf(each.getCharge())+" <BR>\n";

                      }

                     

                      result += "<P>Amount owed is <EM>"+String .valueOf(getTotalCharge()+"\n</EM></p>");

                      result +="<Em>you earned "+String.valueof(getTotalFrequencyRentalPoints())+" frequnecty rentar points</em>";

                      return result;

                 }

           }

}\

本重构的关键在于:运用Extract Method  将两个函数的不同部分提炼出来,从而将相似的代码和变动的代码分开,每次提炼后,我就建立一个签名相同但本体不同的函数

   class Statement(){}

      class TextStatement() extends Statement{}

      class HtmlStatement() extends Statement{}

     

      class Customer{

        public String statement(){

           return  new  TextSatement().value(this);

        }

        public String htmlstatement(){

           return new HtmlStatement().value(this);

        }

      }

      class TextStatement{

        String headerString(Customer customer){

           return "Rental Record for "+customer.getName()+"\n";

        }

        public void value(Customer customer){

           Enumeration rentals = _rentals.elements();

           String result = headerString(custmomer);

           while(rentals.hasMoreElements()){

              Rental rental = (Rental)rentals.nextElement();

             

              result += eachRetalsString();

           }

          

           result +=footerString(custmomer);

           return result;

        }

        String eachRetalsString(Rental aRental){

           return "\t"+aRental.getMovie().getTitle()+"\t"+String.valueOf(aRental.getCharge())+" \n";

        }

        String footerString(Customer aCustomer){

           result += "Amount owed is "+String .valueOf(getTotalCharge()+"\n") +

                 "you earned "+String.valueof(getTotalFrequencyRentalPoints())+" frequnecty rentar points";

        }

      }

     

      class htmlStatement{

        String headerString(Customer custmomer){

           return "<H1>Rental Record for <EM>"+custmomer.getName()+"</EM></H1><P>\n";

        }

        public void value(Customer customer){

           Enumeration rentals = _rentals.elements();

           String result = headerString(custmomer);

           while(rentals.hasMoreElements()){

              Rental rental = (Rental)rentals.nextElement();

             

              result += eachRentalsString(rental);

           }

          

           result +=footerString(customer);

           return result;

        }

        String eachRentalsString(Rentals aRentals){

           return  "\t"+aRentals.getMovie().getTitle()+"\t"+String.valueOf(aRentals.getCharge())+" <BR>\n";

        }

        String footerString(Customer customer){

           result += "<P>Amount owed is <EM>"+String .valueOf(getTotalCharge()+"\n</EM></p>");

           +"<Em>you earned "+String.valueof(getTotalFrequencyRentalPoints())+" frequnecty rentar points</em>";

        }

      }

 

两个Value非常相似了,因此我可以使用pull up method(322)

将它们提升到超类中,提升完毕后,我需要在超类中把子类函数声明为抽象函数

 

 

 

 

      class Statement{

                 public void value(Customer customer){

                      Enumeration rentals = _rentals.elements();

                      String result = headerString(custmomer);

                      while(rentals.hasMoreElements()){

                            Rental rental = (Rental)rentals.nextElement();

                           

                            result += eachRetalsString();

                      }

                     

                      result +=footerString(custmomer);

                      return result;

                 }

                

                 abstract String headrString(Customer customer);

                 abstract String eachRentalString(Rentals rentals);

                 abstract String footerString(Customer customer);

           }

之后把TextStatement.value()函数和htmlStatement.value()函数拿掉,

建立statement子类,复写3个抽象方法

 

Substitute Algorithm(替换算法)

你想要把某个算法替换为另一个更清晰的算法

将函数本体替换为另一个算法

 

String foundPerson(String[] people){

                

                 for(int i=0;i<people.length;i++){

                      if(people[i].equals("Don")){

                            return "Don";

                      }

                      if(people[i].equals("John")){

                            return "John";

                      }

                      if(people[i].equals("Kent")){

                            return "Kent";

                      }

                 }

                 return "";

           }

 

 

 

      String foundPerson(String[] people){

                

                 List candidates = Arrays.asList(new String[]{"Don","John","Kent"});

                 for(int i=0;i<people.length;i++){

                      if(candidates.contains(people[i])){

                            return people[i];

                      }

                 }

                 return "";

                

           }

 

做法:

1. 准备好另一个(替换用)算法,让它通过编译

2. 针对现有测试,执行上述新算法,如果结果与原版本相同, 重构结束

3. 如果测试结果不同于原先,在测试和调试过程中,以旧算法为比较参数标准

3.1.3

如果两个毫不相关的类出现了Duplicated Code,你应该考虑对其中一个使用Extract Class(149)

将重复代码提炼到一个独立类中,然后在另一类内使用这个新类。但是,重复代码所在的函数也可能的确只应该属于某个类,另一个类只能调用它,抑或这个函数可能属于第三个类。而另两个类应该引用第三个类

 

Extract Class (提炼类)

某个类做了应该由两个类做的事,

建立一个新类,将相关的字段和函数从旧类搬移到新类

 

随着类的函数功能不断增加,这个类会变得过分复杂,

很快,你的类就会变成一团乱麻。

 这样的类往往含有大量函数和数据,这样的类往往太大而不易理解,此时你需要考虑哪些部分可以分离出去,并将它们分离到一个单独的类中。

 

做法:

  1. 1.     决定如何分解类所负的责任
  2. 2.     建立一个新类,用以表现从旧类分离出来的责任。
  3. 3.     建立“从旧类访问新类”的连接关系
  4. 4.     对于你想搬移的每一个字段,运动Move Field(146)搬移之、
  5. 5.     每次搬移后,编译,测试
  6. 6.     使用Move Method142)将必要函数搬移到新类。先搬移较低层函数(也就是“被其他函数调用“多于”调用其他函数者“),

再搬移较高层函数。

  1. 7.     每次搬移之后,编译,测试
  2. 8.     检查,精简每个类的接口。
  3. 9.     决定是否公开新类,如果你的确需要公开它,就要决定让它成为引用对象还是不可变的值对象

 

范例:

 

class Person{

     

      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;

      }

     

      private String _name;

      private String _officeAreaCode;

      private String _officeNumber;

     

}

2.将电话号码分离到一个独立的类

 

class TelephoneNumber{

}

建立PersonTelephoneNumber的连接

class Person{

     

      private TelephoneNumber _officeTelephone  = new TelephoneNumber();

 

运动move Field移动字段

class TelephoneNumber{

     

      String getAreaCode(){

           return _areaCode;

      }

     

      void setAreaCode(String arg){

           _areaCode = arg;

      }

     

      private String _areaCode;

}

 

 

class Person{

     

      private TelephoneNumber _officeTelephone  = new TelephoneNumber();

      public String getName(){

           return _name;

      }

      public String getTelephoneNumber(){

           return ("("+getOfficeAreaCode()+")"+_officeNumber);

      }

     

      String getOfficeAreaCode(){

           return _officeTelephone.getAreaCode();

      }

     

      void setOfficeAreaCode(String arg){

           _officeTelephone.setAreaCode(arg);

      }

     

      String getOfficeNumber(){

           return _officeNumber;         

      }

     

      void setOfficeNumber(String arg){

           _officeNumber = arg;

      }

     

      private String _name;

      private String _officeAreaCode;

      private String _officeNumber;

     

}

class TelephoneNumber{

     

      String getAreaCode(){

           return _areaCode;

      }

     

      void setAreaCode(String arg){

           _areaCode = arg;

      }

     

      private String _areaCode;

}

class Person{

     

      private TelephoneNumber _officeTelephone  = new TelephoneNumber();

      public String getName(){

           return _name;

      }

      public String getTelephoneNumber(){

           return _officeTelephone.getTelephoneNumber();

      }

      TelephoneNumber getOfficeTelephone(){

           return _officeTelephone;

      }

private String _name;

      private TelephoneNumber _officeTelephone  = new TelephoneNumber();

 

 

 

 

class TelephoneNumber{

     

      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;

      }

     

      private String _areaCode;

      private String _number;

}

 

      

 

 

Move Method(搬移函数)

你的程序中,有个函数与其所驻类之外的另一个类进行更多交流,

调用后者,或被后者调用

 

在该函数最常引用的类中建立一个有着类似行为的新函数。将旧函数变成一个单纯的委托函数,或是将旧函数完全移除。

 

做法:

1.检查源类中被源函数所使用的一切特性(包括字段和函数),考虑    它们是否也该被搬移。

2.检查源类的子类和超类,看看是否有该函数的其他声明。

3.在目标类中声明这个函数。

4.将源函数的代码复制到目标函数中,调整后者,使其能在新家中正常运行。

5.编译目标类

6.决定如何从源函数正确引用目标对象。

7.修改源函数,使之成为一个纯委托函数

8.决定是否删源函数,或将它作为一个委托函数保留下来

9.如果要移除源函数,请将源类中对源函数的所有调用,替换为对目标函数的调用

1

class Account{

      double overdraftCharge(){

           if(_type.isPremium()){

                 double result = 10;

                 if(_daysOverdrawn > 7){

                      result += (_daysOverdrawn - 7) * 0.85;

                      return result;

                 }

                 else{

                      return  _daysOverdrawn * 1.75;

                 }

           }

      }

     

      double bankCharage(){

           double result = 4.5;

           if(_daysOverdrawn > 0){

                 result += overdraftCharge();

                 return      result;

           }

      }

     

      private AccountType _type;

      private int _daysOverdrawn;

}

 

2.搬移

class AccountType{

      double overdraftCharge(){

           if(isPremium()){

                 double result = 10;

                 if(daysOverdrawn > 7){

                      result += (daysOverdrawn - 7) * 0.85;

                      return result;

                 }

                 else{

                      return  daysOverdrawn * 1.75;

                 }

           }}

 

 

3.委托:

class Account{

      double overdraftCharge(){

                 return _type.overdraftCharge(_daysOverdrawn);

           }

}

 

 

4.找到原函数的调用者,并且重新定向

class Account{

      double bankCharage(){

           double result = 4.5;

           if(_daysOverdrawn > 0){

                 result += _type.overdraftCharge(_daysOverdrawn);

                 return      result;

           }

      }

     

}

 

 

 

Move Field(搬移字段)

你的程序中,某个字段被其所驻类之外的另一个类更多地用到

在目标类新建一个字段,修改源字段的所有用户,令它们改用新字段。

 

做法:

  1. 1.     如果字段的访问级是public ,使用Encapsulate Field(206)将它封装起来。
  2. 2.     在目标类中建立与源字段相同的字段,并同时建立相应的设值/取值函数
  3. 3.     编译目标类
  4. 4.     决定如何在源对象中引用目标对象
  5. 5.     删除源字段
  6. 6.     将所有对源字段的引用替换为对某个目标函数的调用

 

class Account{

      private AccountType _type;

      private double _interestRate;

     

      double interestForAmount_days(double amount, int days){

           return _interestRate * amount * days/365;

      }

}

class AccountType{

      private double _interestRate;

     

      void setInterestRate(double arg){

           _interestRate = arg;

      }

     

      double getInterestRate(){

           return _interestRate;

      }

}

 

class Account{

      private AccountType _type;

     

      double interestForAmount_days(double amount, int days){

           return _type.getInterestRate() * amount * days / 365;

      }

}

 

 

 

 

范例:使用Self-Encapsulation

如果有很多的函数已经使用了_interestRate字段,我应该先运用SelfEncapsulation(171)自我封装:

class Account{

      private AcountType _type;

      private double _interestRate;

     

      double interestForAmount_days(double amount,int days){

           return getInterestRate() * amount * days / 365;

      }

      private void setInterestRate(double arg){

           _interestRate = arg;

      }

      private double getInterestRate(){

           return _interestRate;

      }

}

 

 

 

class Account{

      private AcountType _type;

      private double _interestRate;

     

      double interestForAmount_days(double amount,int days){

           return getInterestRate() * amount * days / 365;

      }

      private void setInterestRate(double arg){

           _type.setInterestRate(arg);

      }

      private double getInterestRate(){

           return _type.getInterestRate();

      }

}

 

 

 

 

 

 

 

 

Self Encapsulate Field(自我封装字段)

你直接访问一个字段,但与字段之间的耦合关系逐渐变得笨拙

为这个字段建立取值/设置函数,并且只以这些函数来访问字段

private int _low,_hight;

boolean includes(int arg){

      return arg>=_low && arg<=_hight;

}

 

private int _low,_hight;

boolean includes(int arg){

      return arg>=getLow() && arg<=getHight();

}

int getLow(){

      return _low;

}

int getHight(){

      return _high;

}

 

 

 

 

如果你想访问超类中的一个字段,却又想在子类中将对这个变量的访问改为一个计算后的值,这就是最该使用selfEncapsulate Field的时候,“字段自我封装”只是第一步,完成自我封装之后,你可以在子类中  根据自己的需要随意覆写取值/设值函数

 

做法:

1、   为特地封装字段建立取值 /设值函数

2、   找出该字段的所有引用点,将它们全部改为调用取值 /设值函数

3、   将字段声明为private

4、   复查,确保找出所有引用点

5、   编译 ,测试

 

 

范例

class IntRange{

     

      private int _low,_high;

     

      boolean includes(int arg){

           return arg >= _low && arg <=_high;

      }

          

      void grow(int factor){

           _high = _high * factor;

      }

     

      IntRange(int low,int high){

           _low = low;

           _high = high;

      }

}

 

 

class IntRange{

     

      boolean includes(int arg){

           return arg >= getLow() && arg<= getHigh();

      }

     

      void grow(int factor){

           setHigh(getHigh() * factor);

      }

     

      private int _low, _high;

     

      int getLow(){

           return _low;

      }

     

      int getHigh(){

           return _high;

      }

     

      void setHigh(int arg){

           _high = arg;

      }

     

      void setLow(int arg){

           _low = arg;

      }

}

 

 

 

 

 

class IntRange{

     

      boolean includes(int arg){

           return arg >= getLow() && arg<= getHigh();

      }

     

      void grow(int factor){

           setHigh(getHigh() * factor);

      }

     

      private int _low, _high;

     

      int getLow(){

           return _low;

      }

     

      int getHigh(){

           return _high;

      }

     

      void setHigh(int arg){

           _high = arg;

      }

     

      void setLow(int arg){

           _low = arg;

      }

     

      IntRange(int low,int high){

           initialize(low,high);

      }

      private void initialize(int low,int high){

           _low =low;

           _high = high;

      }

}

 

一旦拥有子类,上述的所有动作的价值就体现出来了

class CappedRange extends IntRange{

     

      CappedRange(int low,int high,int cap){

           super(low,high);

           _cap = cap;

      }

     

      private int _cap;

     

      int getCap(){

           return _cap;

      }

     

      int getHigh(){

           return Math.min(super.getHigh(),getCap());

      }

}

 

覆写getHigh(),从而加入对范围上限的考虑

 

 

 

 

3.2 Long Method(过长的函数)

程序越长越难理解。应该积极的分解函数。

在百分之九十九的场合里,要把函数变小,只需要Extract Method(110)

 

如果函数内有大量的参数和临时变量,你可以经常运用

Replace Temp with Query(以查询取代临时变量)来消除临时变量,

Introduce Parameter Object (引入参数对象),Preserve Whole Object(保持对象完整)则可以将过长的参数列表变得更简洁一些

如果这么做还有大量的临时变量和参数,那就应该使用Replace Method with Method Object(以函数对象取代函数)

 

 

 

 

 

 

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

你的程序以一个临时变量保存某一表达式的运算结果

将这个表达式提炼到一个独立函数中,将这个临时变量的所有引用点替换对新函数的调用。

此后,新函数就可被其他函数使用

 

double basePrice = _quantity * _itemPrice;

if(basePrice > 1000){

      return basePrice * 0.95;

}else{

      return basePrice * 0.98;

}

 

 

if(basePrice() >1000){

      return basePrice() * 0.95;

}else{

      return basePrice() * 0.98;

}

 

double basePrice(){

      return _quantity * _itemPrice;

}

 

 

做法:

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

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

3. 编译

4. 将“对该临时变量赋值”之语句的等号右侧部分提炼到一个独立函数中

5. 编译测试

6. 在该临时变量身上实施Inline Temp

 

范例:

double getPrice(){

      int basePrice = _quantity * _itemPrice;

      double discountFactor;

      if(basePrice > 1000){

           discountFactor = 0.95;

      }else{

           discountFactor = 0.98;

      }

      return basePrice * discountFactor;

}

 

 

 

将变量声明为final

double getPrice(){

      final int basePrice = _quantity * _itemPrice;

      final double discountFactor;

      if(basePrice > 1000){

           discountFactor = 0.95;

      }else{

           discountFactor = 0.98;

      }

      return basePrice * discountFactor;

}

如果有任何问题,编译器就会警告我,之所以先做这件事,因为如果临时变量不只被赋值一次。

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;

}

 

Inline Temp(119)首先把临时变量basePrice第一个引用替换调

double getPrice(){

      final int basePrice = basePrice();

      final double discountFactor;

      if(basePrice() > 1000){

           discountFactor = 0.95;

      }else{

           discountFactor = 0.98;

      }

      return basePrice * discountFactor;

}

 

 

 

double getPrice(){

      final int basePrice = basePrice();

      final double discountFactor = discountFactor();

 

      return basePrice() * discountFactor;

}

private int basePrice(){

      return _quantity * _itemPrice;

}

private int discountFactor(){

      if(basePrice() > 1000){

           discountFactor = 0.95;

      }else{

           discountFactor = 0.98;

      }

}

 

 

 

double getPrice(){

 

      return basePrice() * discountFactor();

}

private int basePrice(){

      return _quantity * _itemPrice;

}

private int discountFactor(){

      if(basePrice() > 1000){

           discountFactor = 0.95;

      }else{

           discountFactor = 0.98;

      }

}

 

 

 

InLine Temp (内联临时变量)

你有一个临时变量,只被一个简单表达式赋值一次,而它妨碍了其他重构的手法

将所有对该变量的引用动作,替换为对它赋值的那个表达式自身

 

 

 

double basePrice = anOrder.basePrice();

return (basePrice >1000);

 

 

return (anOrder.basePrice() > 1000);

 

 

做法:

1. 检查给临时变量赋值的语句,确保等号右边的表达式没有副作用。

2. 如果这个临时变量并未被声明为final,那就将它声明为final,然后编译

3. 找到该临时变量的所有引用点,将它们替换为“为临时变量赋值”的表达式

4. 每次修改后,编译并测试

5. 修改完所有引用点之后,删除该临时变量的声明和赋值语句

6. 编译,测试

 

 

 

 

 

 

 

 

 

 

Introduce Parameter Object (引入参数对象)

某些参数总是很自然地同时出现。

以一个对象取代这些参数

 

 

做法:

  1. 1.     新建一个类,用以表现你想替换的一组参数,将这个类设为不可变
  2. 2.     编译
  3. 3.     针对使用该组参数的所有函数,实施Add Parameter275),

传入上述新建类的实例对象,并将此参数值设为null

  1. 4.     对于Data Clumps中的每一项(在此均为参数),从函数签名中移除之,并修改调用端和函数本体,令它们都改而通过新的参数对象取得该值
  2. 5.     每去除一个参数,编译并测试
  3. 6.     将原先的参数全部去除之后,观察有无适当函数可以运用Move method142)搬移到参数对象之中



 

 

 

class Entry{

     

      Entry(double value,Date chargeDate){

           _value = value;

           _chargeDate = chargeDate;

      }

     

      Date getDate(){

           return _chargeDate;

      }

      double getValue(){

           return _value;

      }

     

      private Date _chargeDate;

      private double _value;

}

 

double  flow = anAccount.getFlowBetween(startDate,endDate);

 

 

 

声明一个简单的容器,表示范围

class DateRange{

     

      DateRange(Date start,Date end){

           _start = start;

           _end = end;

      }

     

      Date getStart(){

           return _start;

      }

     

      Date getEnd(){

           return _end;

      }

     

      private final Date _start;

      private final Date _end;

}

 

 

 

把DateRange加入到参数列表

class Account {

 

      double getFlowBetween(Date start, Date end,DateRange range) {

           double result = 0;

           Enumeration e = _entries.elements();

           while (e.hasMoreElements()) {

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

                 if (each.getDate().equals(start)

                            || each.getDate().equals(end)

                            || (each.getDate().after(start) && each.getDate().before(

                                       end))) {

                      result += each.getValue();

                 }

           }

           return result ;

      }

      private Vector _entries  = new Vector();

}

 

double  flow = anAccount.getFlowBetween(startDate,endDate,null);

 

去除旧参数之一,以新建对象取而待之

class Account {

 

      double getFlowBetween(Date end,DateRange range) {

           double result = 0;

           Enumeration e = _entries.elements();

           while (e.hasMoreElements()) {

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

                 if (each.getDate().equals(range.getStart())

                            || each.getDate().equals(end)

                            || (each.getDate().after(range.getStart()   ) && each.getDate().before(

                                       end))) {

                      result += each.getValue();

                 }

           }

           return result ;

      }

      private Vector _entries  = new Vector();

}

 

double  flow = anAccount.getFlowBetween(endDate,new DateRange(startDate,null));

 

 

 

class Account {

 

      double getFlowBetween(DateRange range) {

           double result = 0;

           Enumeration e = _entries.elements();

           while (e.hasMoreElements()) {

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

                 if (each.getDate().equals(range.getStart())

                            || each.getDate().equals(range.getEnd())

                            || (each.getDate().after(range.getStart()   ) && each.getDate().before(

                                       range.getEnd()))) {

                      result += each.getValue();

                 }

           }

           return result ;

      }

      private Vector _entries  = new Vector();

}

 

double  flow = anAccount.getFlowBetween(new DateRange(startDate,endDate));

 

 

利用Extract Method 和 move Method

class Account {

 

      double getFlowBetween(DateRange range) {

           double result = 0;

           Enumeration e = _entries.elements();

           while (e.hasMoreElements()) {

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

                 if(range.includes(each.getDate())){

                      result +=each.getValue();

                 }

           }

           return result ;

      }

      private Vector _entries  = new Vector();

}

 

double  flow = anAccount.getFlowBetween(new DateRange(startDate,endDate));

class DateRange{

      boolean  includes(Date arg){

           return  (arg.equals(_start)

                      || arg.equals(_end)

                      || (arg.after(_start) && arg.getDate().before(

                                  _end))) {

           }

      }

      DateRange(Date start,Date end){

           _start = start;

           _end = end;

      }

     

      Date getStart(){

           return _start;

      }

     

      Date getEnd(){

           return _end;

      }

     

      private final Date _start;

      private final Date _end;

}

 

 

 

 

Preserve Whole Object(保持对象完整)

你从某个对象中取出若干值,将他们作为某一次函数调用时的参数。

改为传递整个对象。

 

 

int low = daysTempRange().getLow();

           int high = daysTempRange().getHigh();

           withinPlan =plan.withinRange(low,high);

          

           withinPlan = plan.withinRange(daysTempRange());

 

做法:

  1. 1.     对你目标函数新添一个参数项,用以代表原数据所在的完整对象
  2. 2.     编译,测试
  3. 3.     判断哪些参数可被包含在新添的完整对象中
  4. 4.     选择上述参数之一,将被调用函数中原引用该参数的地方,改为调用新添参数对象的相应取值函数
  5. 5.     删除该项参数
  6. 6.     编译,测试
  7. 7.     针对所有可从完整对象中获取的参数,重复上述过程。
  8. 8.     删除调用端中那些带有被删除参数的代码
  9. 9.     编译,测试

 

范例:

class Room{

      boolean withinPlan(HeatingPlan plan){

           int low = daysTempRange().getLow();

           int high = daysTempRange().getHigh();

           return plan.withinRange(low,high);

      }

}

class HeatingPlan{

      boolean withinRange(int low,int high){

           return (low >= _range.getLow() && high<=_range.getHigh());

      }

      private Template _range;

}

 

 

class Room{

      boolean withinPlan(HeatingPlan plan){

           int low = daysTempRange().getLow();

           int high = daysTempRange().getHigh();

           return plan.withinRange(daysTempRange(),low,high);

      }

}

class HeatingPlan{

      boolean withinRange(TempRange roomRange,int low,int high){

           return (low >= _range.getLow() && high<=_range.getHigh());

      }

      private Template _range;

}

 

 

 

class Room{

      boolean withinPlan(HeatingPlan plan){

           return plan.withinRange(daysTempRange());

      }

}

class HeatingPlan{

      boolean withinRange(TempRange roomRange){

           return (roomRange.getLow() >= _range.getLow() && roomRange.getHigh()<=_range.getHigh());

      }

      private Template _range;

}

 

 

 

Replace Method with Method Object(以函数对象取代函数)

你有一个大型函数,其中对局部变量的使用使你无法采用Extract Method(110);

将这个函数放进一个单独的对象中,如此一来局部变量就成了对象内的字段。然后你可以在同一个对象中将这个大型函数分解为多个小型函数

 

 

做法:

1. 建立一个新类,根据待处理函数的用途,为这个类命名。

2. 在新类中建立一个final字段,用以保存原先大型函数所在的对象。我们将这个字段称为“源对象”。同时,针对原函数的每个临时变量和每个参数,在新类中建立一个对应的字段保存之。

3. 在新类中建立一个构造函数,接受源对象及原函数的所有参数作为参数

4. 在新类中建立一个compute()函数

5. 将原函数的代码复制到compute()函数中,如果需要调用源对象的任何函数,请通过源对象字段调用。

6. 编译

7. 将旧函数的函数本体替换为这样一条语句:“创建上述新类的一个新对象,而后调用其中的compute()函数”

 

 

 

 

 

class Account{

          

      int gamma(int inputVal,int quantity, int yearToDate){

           int importantValue1 = (inputVal * quantity)+delta();

           int importantValue2 = (inputVal * yearToDate)+100;

          

           if((yearToDate - importantValue1) >100){

                 importantValue2 -= 20;

           }

           int  importantValue3 = importantValue2 * 7 ;

           return importantValue3 - 2 * importantValue1;

      }

}

建立一个新类,在新类中建立一个final字段,用以保存原先大型函数所在的对象。我们将这个字段称为“源对象”。同时,针对原函数的每个临时变量和每个参数,在新类中建立一个对应的字段保存之。

 

class Gamma{

      private final Account _account;

      private int inputVal;

      private int quantity;

      private int yearToDate;

      private int importantValue1;

      private int importantValue2;

      private int importantValue3;

}

 

 

加入构造函数

class Gamma{

      private final Account _account;

      private int inputVal;

      private int quantity;

      private int yearToDate;

      private int importantValue1;

      private int importantValue2;

      private int importantValue3;

     

      Gamma(Account source,int inputValArg,int quantityArg,int yearToDateArg){

           _account  =source;

           inputVal = inputValArg;

           quantity = quantityArg;

           yearToDate = yearToDateArg;

      }

}

 

把原本的函数搬到compute,函数中调用Account类的地方。

class Gamma{

      private final Account _account;

      private int inputVal;

      private int quantity;

      private int yearToDate;

      private int importantValue1;

      private int importantValue2;

      private int importantValue3;

     

      Gamma(Account source,int inputValArg,int quantityArg,int yearToDateArg){

           _account  =source;

           inputVal = inputValArg;

           quantity = quantityArg;

           yearToDate = yearToDateArg;

      }

     

      int compute(int inputVal,int quantity, int yearToDate){

           int importantValue1 = (inputVal * quantity)+_account.delta();

           int importantValue2 = (inputVal * yearToDate)+100;

          

           if((yearToDate - importantValue1) >100){

                 importantValue2 -= 20;

           }

           int  importantValue3 = importantValue2 * 7 ;

           return importantValue3 - 2 * importantValue1;

      }

}

  

修改原函数

 

class Account{

          

      int gamma(int inputVal,int quantity, int yearToDate){

           return new Gamma(this,inputVal,quantity,yearToDate).compute();

      }

}

 

Extract Method();

int compute(){

           int importantValue1 = (inputVal * quantity)+_account.delta();

           int importantValue2 = (inputVal * yearToDate)+100;

          

           importantThing();

 

           int  importantValue3 = importantValue2 * 7 ;

           return importantValue3 - 2 * importantValue1;

      }

 

      private void importantThing() {

           if((yearToDate - importantValue1) >100){

                 importantValue2 -= 20;

           }

      }

 

 

 

3.3 Large Class (过大的类)

和拥有太多实例变量的一样,一个类如果拥有太多的代码,往往也适合使用Extract Class和Extract subclass.

可以运用Extract Interface(341)为每一种使用方式提炼出一个接口,可以帮你指定要想的方法

 

Extract Subclass(提炼子类)

类中某些特性只被某些(而非全部)实例用到

新建一个子类,将上面所说的那一部分特性移到子类中

 

动机:

你发现类中的某些行为只被一部分实例用到,其他实例不需要它们

 

 

 

做法:

  1. 1.     将源类定义一个新的子类
  2. 2.     为这个新的子类提供构造函数
  3. 3.     找出调用超类构造函数的所有地点。如果它们需要的是新建的子类,令它们改而调用新构造函数
  4. 4.     逐一使用push down method(328)push down field(329)将源类的特性移到子类去
  5. 5.     找到所有这样的字段;它们所传达的信息如今可由继承体系自身传达(这一类字段通常是boolean变量或类型码)。以self Encapsulate Field171)避免直接使用这些字段,然后将它们的取值函数替换为多态常量函数。所有使用这些字段的地方都应该以Replace Conditional with Polymorphism255)重构
  6. 6.     每次下移之后,编译并测试

 

 

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);

           }

      }

 

  1. 找出JobItem构造函数调用地方,修改

      JobItem j1 = new JobItem(0,5,true,kent);

JobItem j1 = new LaborItem(0,5,true,kent);

 

 

  1. 针对每个构造函数使用rename Method(273)

我要新建一个构造函数,并把旧构造函数声明为protected

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);

}

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

JobItem j1 = new JobItem(10, 15);

 

  1. 修改子类构造器

class LaborItem extends JobItem{

          

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

                 super(unitPrice,quantity,isLabor,employee);

           }

          

           public LaborItem(int quantity,Employee emoloyee){

                 super(0,quantity,true,employee);

            }

}

 

7.将JobItem的特性向下搬移

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);

      }

     

      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;

      protected  Employee _employee;

      private boolean  _isLabor;

}

 

      class LaborItem extends JobItem{

          

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

                 super(unitPrice,quantity,isLabor,employee);

           }

          

           public LaborItem(int quantity,Employee emoloyee){

                 super(0,quantity,true,employee);

           }

          

           public Employee getEmployee(){

                 return _employee;

           }

          

}

 

 

 

8.

class JobItem{

     

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

           _unitPrice = unitPrice;

           _quantity = quantity;

           _isLabor = isLabor;

           _employee = employee;

}

 

 

9.将employee变成protect ,再次清理构造函数

      class LaborItem extends JobItem{

                     

           public LaborItem(int quantity,Employee employee){

                 super(0,quantity,true);

                 _employee = employee;

           }

 

 

10._isLabor字段是继承体系内在信息,因此我可以移除这个字段了,

 

class JobItem{

      protected boolean isLabor(){

           return false;

      }

      class LaborItem extends JobItem{

           protected boolean isLabor(){

                 return true;

           }

      }

 

 

  1. 找到

class JobItem{

 

      public int getUnitPrice(){

           return (_isLabor) ? _employee.getRate():_unitPrice;

      }

 

 

 

 

 

11. 重构为:

class JobItem{

      public int getUnitPrice(){

           return _unitPrice;

}

 

12.

      class LaborItem extends JobItem{

                

           public int getUnitPrice(){

                 return _employee.getRate();

      }     }

 

 

 

 

 

 

Push Down Method(函数下移)

超类中的某个函数只与部分(而非全部)子类有关

将这个函数移到相关的那些子类去

 

 

 

 

做法:

  1. 1.     在所有子类中声明该函数,将超类中的函数本体赋值到每一个子类函数中
  2. 2.     删除超类中的函数
  3. 3.     编译,测试
  4. 4.     将该函数从所有不需要它的那些子类中删掉
  5. 5.     编译测试

 

 

 

 

 

 

 

 

 

Push Down Field(字段下移)

超类中的某个字段只被部分(而非全部)子类用到

将这个字段移到需要它的那些子类去

 

 

 

 

 

做法:

  1. 1.     在所有子类中声明该字段
  2. 2.     将该字段从超类中移除
  3. 3.     编译,测试
  4. 4.     将该字段从所有不需要它的那些子类中删掉
  5. 5.     编译,删除

 

 

 

 

 

Replace Conditional with Polymorphism(以多态取代条件表达式)

你手上有个条件表达式,它根据对象类型的不同而选择不同的行为

将这个条件表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数

 

      double getSpeed(){

           switch(_type){

                 case EUROPEAN:

                      return getBaseSpeed();

                 case AFRICAN:

                      return getBaseSpeed() - getLoadFactor() * _numberOfCocounts;

                 case NORWEGIAN_BLUE:

                      return (_isNailed)?0:getBaseSpeed(_voltage);

           }

           throw new RuntimeException("should be unreachable");

      }

 

 

 

 

 

 

如果你需要根据对象的不同类型而采取不同的行为,多态使你不必编写明显的条件表达式

 

 

做法:

首先必须是一个继承结构

要建立继承结构,有两种选择:

Replace Type code with subclasses(223)replace Type code with state/Starategy(227)

 

  1. 1.     如果要处理的条件表达式是一个更大函数中的一部分,首先对条件表达式进行分析,然后使用Extract Method(110)将它提炼到一个独立的函数去
  2. 2.     如果有必要,使用move method142)将条件表达式放置到继承结构的顶端。
  3. 3.     任选一个子类,在其中建立一个函数,使之覆写超类中容纳条件表达式的那个函数,将与子类相关的条件表达式分支复制到新建函数中,并对它做适当的调整
  4. 4.     编译测试
  5. 5.     在超类中删掉条件表达式内被复制了的分支
  6. 6.     编译测试
  7. 7.     针对条件表达式的每个分支,重复上述过程,直到所有分支都被移动到子类内的函数为止
  8. 8.     将超类之中容纳条件表达式的函数声明为抽象函数

 

 

 

 

范例:

1.

class Employee{

     

      int payAmount(){

           switch(getType()){

                 case EmployeeType.ENGINEER:

                      return _monthlySalary;

                 case EmployeeType.SALESMAN:

                      return _monthlySalary + _commission;

                 case EmployeeType.MANAGER:

                      return _monthlySalary + _bonus;

                 default:

                      throw new RuntimeException("Incorrect Employee");

           }

      }

     

      int getType(){

           return _type.getTypeCode();

      }

     

      private EmployeeType _type;

     

     

}

abstract class EmployeeType{

      abstract int getTypeCode();

}

 

class Engineer extends EmployeeType{

      int getTypeCode(){

           return Employee.ENGINEER;

      }

}

 

 

 

2.提炼移动到EmployeeType

abstract class EmployeeType{

      abstract int getTypeCode();

     

      int payAmount(Employee emp){

           switch(getTypeCode()){

                 case EmployeeType.ENGINEER:

                      return emp.getMonthlySalary();

                 case EmployeeType.SALESMAN:

                      return emp.getMonthlySalary() + emp.getCommission();

                 case EmployeeType.MANAGER:

                      return emp.getMonthlySalary() + emp.getBonus();

                 default:

                      throw new RuntimeException("Incorrect Employee");

           }

}

 

 

 

3.委托EmployeeType

class Employee{

     

      int payAmount(){

           return _type.payAmount(this);

      }

 

 

4. .switch的分支复制到Engineer

class Engineer extends EmployeeType{

      int payAmount(Employee emp){

           return emp.getMonthlySalary();

      }

}

 

 

  1. 检查替代子类能否工作
    abstract class EmployeeType{

      abstract int getTypeCode();

     

      int payAmount(Employee emp){

           switch(getTypeCode()){

                 case EmployeeType.ENGINEER:

                      return      throw new RuntimeException("should be being overridden");

                 case EmployeeType.SALESMAN:

                      return emp.getMonthlySalary() + emp.getCommission();

                 case EmployeeType.MANAGER:

                      return emp.getMonthlySalary() + emp.getBonus();

                 default:

                      throw new RuntimeException("Incorrect Employee");

           }

}

 

 

6.switch的分支复制到Engineer

class Engineer extends EmployeeType{

      int payAmount(Employee emp){

           return emp.getMonthlySalary();

      }

}

 

 

7.重复上述过程,去除分支

class Salesman{

      int payAmount(Employee emp){

           return emp.getMonthlySalary() + emp.getCommission();

      }

}

class Manager{

      int payAmount(Employee emp){

           return  emp.getMonthlySalary() + emp.getBonus();

      }

}

 

  1. 9.     将超类声明为抽象函数

abstract class EmployeeType{

      abstract int getTypeCode();

     

      abstract int payAmount(Employee emp){\

          

}

 

 

 

 

Replace Type Code with Subclasses(以子类取代类型码)

你有一个不可变的类型码,它会影响类的行为

以子类取代这个类型码

 

 

 

 

如果你面对的类型码不会影响宿主类的行为,可以使用Replace Type Code with Class(218)来处理它们。但如果类型码会影响宿主类的行为,那么最好的办法就是借助多态来处理变化行为。

 

Replace Type Code with Subclasses(223)的好处在于:它把对不同行为的子类,从类用户那转移到子类自身。如果需要加入新的行为变化,只需添加一个子类就行了。如果没有多态机制,就必须找到所有条件表达式,并逐一修改它们。因此,如果未来还有可能加入新行为,这项重构将特别的有价值

 

 

做法:

1.         使用Self Encapsulate Field171)将类型码自我封装起来

2.         为类型码的每一个数值建立一个相应的子类。在每个子类中覆写类型码的取值函数,使其返回相应的类型码值。

3.         每建立一个新的子类,编译并测试。

4.         从超类中删掉保存类型码的字段。将类型码访问函数声明为抽象函数

5.         编译测试

 

 

1.

class Employee{

      private int _type;

      static final int ENGINEER = 0;

      static final int SALESMAN = 1;

      static final int MANAGER = 2;

     

      Employee(int type){

           _type = type;

  }

 

2、将类型码自我封装起来

class Employee{

      private int _type;

      static final int ENGINEER = 0;

      static final int SALESMAN = 1;

      static final int MANAGER = 2;

     

      Employee(int type){

           _type = type;

      }

     

      int getType(){

           return _type;

      }

}

 

 

 

3.建立工厂函数

class Employee{

      private int _type;

      static final int ENGINEER = 0;

      static final int SALESMAN = 1;

      static final int MANAGER = 2;

     

      static Employee create(int type){

           return new Employee(type);

      }

      private Employee(int type){

           _type = type;

      }

     

      int getType(){

           return _type;

      }

}

 

 

 

 

4.建立子类,并覆写类型码取值函数

class Engineer extends Employee{

     

      int getType(){

           return Employee.ENGINEER;

      }

}

 

 

 

5.修改工厂函数,令它返回一个合适的对象

class Employee{

      private int _type;

      static final int ENGINEER = 0;

      static final int SALESMAN = 1;

      static final int MANAGER = 2;

     

      static Employee create(int type){

           if(type == ENGINEER ){

                 return new Engineer();

           }else{

                 return new Employee(type);

           }

      }

 

 

 

 

6.移除Employee中保存类型码的字段,并将getType()声明为一个抽象函数

class Employee{

      private int _type;

      static final int ENGINEER = 0;

      static final int SALESMAN = 1;

      static final int MANAGER = 2;

     

      abstract int getType();

      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 IllegalAccessException("Incorrect type code value");

           }

      }

      private Employee(int type){

           _type = type;

      }

}

 

 

 

 

 

 

Replace Type Code with State/Strategy(以State/Strategy取代类型码)

你有一个类型码,它会影响类的行为,但你无法通过继承手法消除它

以状态对象取代类型码

 

 

做法:“

  1. 1.     使用self Encapsulate Field(171)将类型码自我封装起来
  2. 2.     新建一个类,根据类型码的用途为它命名。这就是一个状态对象
  3. 3.     在超类中建立一个抽象的查询函数,用以返回类型码。在每个子类中覆写该函数,返回确切的类型码
  4. 4.     编译
  5. 5.     在源类中建立一个字段,用以保存新建的状态对象
  6. 6.     调整源类中负责查询类型码的函数,将查询动作转发给状态对象
  7. 7.     调整源类中为类型码设置的函数,将一个恰当的状态对象子类赋值给“保存状态对象“的那个字段
  8. 8.     编译测试

 

 

范例:

 

 

1

class Employee{

     

      private int _type;

      static final int ENGINEER = 0;

      static final int SALESMAN = 1;

      static final int MANAGER = 2;

     

      Employee(int type){

           _type = type;

      }

     

      public int getType(){

           switch(_type){

           case ENGINEER:

                 return _monthlySalary;

           case SALESMAN:

                 return _monthlySalary + _commission;

           case MANAGER:

                 return _monthlySalary + _bonus;

           default:

                 throw new RuntimeException("Incorrect Employee");

           }

      }

     

}

 

2.使用self Encapsulate Field(自我封装起来)

class Employee{

     

      private int _type;

      static final int ENGINEER = 0;

      static final int SALESMAN = 1;

      static final int MANAGER = 2;

     

      Employee(int type){

           _type = type;

      }

     

      public int getType(){

           return _type;

      }

     

      public void setType(int arg){

           _type = arg;

      }

      public int getType(){

           switch(getType()){

           case ENGINEER:

                 return _monthlySalary;

           case SALESMAN:

                 return _monthlySalary + _commission;

           case MANAGER:

                 return _monthlySalary + _bonus;

           default:

                 throw new RuntimeException("Incorrect Employee");

           }

      }

     

}

 

 

3.声明状态类,提供抽象函数,返回状态码

abstract class Employee{

      abstract int getTypeCode();

}

 

4.创建子类

class Engineer extends EmployeeType{

     

      int getTypeCode(){

           return Employee.ENGINEER;

      }

}

class Manager extends EmployeeType{

     

      int getTypeCode(){

           return Employee.MANAGER;

      }

}

class Salesman extends EmployeeType{

     

      int getTypeCode(){

           return Employee.SALESMAN;

      }

}

 

 

5.

class Employee{

     

      private EmployeeType _type;

      static final int ENGINEER = 0;

      static final int SALESMAN = 1;

      static final int MANAGER = 2;

     

      Employee(int type){

           _type = type;

      }

     

      public int getType(){

           return _type.getTypeCode();

      }

     

      public void setType(int arg){

           switch(arg){

           case ENGINEER:

                 _type = new Engineer();

                 break;

           case SALESMAN:

                 _type = new Salesman();

                 break;

           case MANAGER:

                 _type = new Manager();

                 break;

           default:

                 throw new RuntimeException("Incorrect Employee Code");

           }

      }

 

}

 

 

6.

class EmployeeType{

     

      static EmployeeType newType(int code){

           switch(code){

           case ENGINEER:

                 return new Engineer();

           case SALESMAN:

                 return new Salesman();

           case MANAGER:

                 return new Manager();

           default:

                 throw new RuntimeException("Incorrect Employee");

           }

      }

     

      static final int ENGINEER = 0;

      static final int SALESMAN = 1;

      static final int MANAGER = 2;

}

 

 

7.

class Employee{

     

      int payAmount(){

           switch(getType()){

           case EmployeeType.ENGINEER;

           return _monthlySalary;

           case EmployeeType.SALESMAN;

           return _monthlySalary + _commission;

           case EmployeeType.MANAGER:

                 return _monthlySalary + _bonus;

           default:

                 throw new RuntimeException("Incorrect Employee");

           }

      }

}

 

 

EReplace Constructor with Factory Method(以工厂函数取代构造函数)

你希望在创建对象时不仅仅是做简单的建构动作

将构造函数替换为工厂函数

 

Emyloyee(int type){

      _type = type;

}

 

static Employee create(int type){

      return new Employee(type);

}

 

在派生子类的过程中以工厂函数取代类型码。你可能常常需要根据类型码创建相应的对象,现在,创建名单中还得加上子类,那些子类也根据类型码来创建。然而由于构造函数只能返回单一类型的对象。因此你需要将构造函数替换为工厂函数

 

 

做法:

1、   新建一个工厂函数,让它调用现有的构造函数

2、   将调用构造函数的代码改为调用工厂函数

3、   每次替换后,编译并测试

4、   将构造函数声明为private

5、   编译

 

 

范例:根据整数(实际是类型码)创建对象

1.

class Employee{

     

      private int _type;

     

      static final int ENGINEER = 0;

      static final int SALESMAN = 1;

      static final int MANAGER = 2;

     

      Employee(int type){

           _type = type;

      }

     

}

 

 

2.根据不同的状态码,创建工厂函数

class Employee{

     

      private int _type;

     

      static final int ENGINEER = 0;

      static final int SALESMAN = 1;

      static final int MANAGER = 2;

     

      Employee(int type){

           _type = type;

      }

     

      static Employee create(int type){

           return new Employee();

      }

}

 

 

3.修改所有调用构造函数的调用点,将构造函数变成private

client code.....

Employee eng = Employee.create(Employee.ENGINEER);

 

 

class Employee{

     

      private int _type;

     

      static final int ENGINEER = 0;

      static final int SALESMAN = 1;

      static final int MANAGER = 2;

     

      private Employee(int type){

           _type = type;

      }

     

      static Employee create(int type){

           return new Employee();

      }

}

 

 

范例:根据字符串创建子类对象

      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");

           }

      }

 

2.绕过switch的最好办法就是使用 Class.forName();

      static Employee create(String name){

           try{

                 return (Employee)Class.forName(name).newInstance();

           }catch(Exception e){

                 throw new IllegalAccessException("Unable to instanceiate "+name);

           }

      }

 

 

 

 

3.调用新版的create的String版本

class Employee{

     

      private int _type;

     

      static final int ENGINEER = 0;

      static final int SALESMAN = 1;

      static final int MANAGER = 2;

     

      private Employee(int type){

           _type = type;

      }

     

      static Employee create(int type){

           switch(type){

           case ENGINEER:

                 return create("Engineer");

           case SALESMAN:

                 return create("salesman");

           case MANAGER:

                 return create("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 IllegalAccessException("Unable to instanceiate "+name);

           }

      }

}

但却因为失去了编译期检验,使得一个小小的拼写错误就可能造成运行期错误

 

范例:以明确函数创建子类

如果你只有少数几个子类,而且它们都不再有变化,这条途径是很有用的

 

1.为每个子类定义一个工厂函数

class Person{

     

      static Person createMale(){

           return new Male();

      }

     

      static Person createFemale(){

           return new Female();

      }

}

 

2.然后调用替换

Person kent = new Male();

Person kent = Person.createMale();

 

 

 

Extract Interface(提炼接口)

若干客户使用类接口中的同一个子集,或者两个类的接口有部分相同

将相同的子集提炼到一个独立接口中

 

 

 

 

 

 

做法:

1. 新建一个空接口

2. 在接口中声明待提炼类的共通操作

3. 让相关的类实现上述接口

4. 调整客户端的类型声明,令其使用该接口

 

1.TimeSheet需要知道员工的级别和员工的特殊技能

class TimeSheet{

     

      double charge(Employee emp,int days){

           int base = emp.getRate() * days;

           if(emp.hasSpecialSkill()){

                 return base * 1.05;

           }else{

                 return base;

           }

      }

}

2.Employee有很多功能,针对需要建立两个功能的接口

interface Billable{

     

      public int getRate();

      public boolean hasSpecialSkill();

     

}

3.声明让Employee实现接口

class Employee implements  Billable

4.修改函数声明,强调Employee只用到这两个功能的

class TimeSheet{

      double charge(Billable emp,int days){

           int base = emp.getRate() * days;

           if(emp.hasSpecialSkill()){

                 return base * 1.05;

           }else{

                 return base;

           }

      }

}

 

 

 

 

 

 

3.4 Long Parameter List(过长的参数列)

如果向已有的对象发出一条请求就可以取代一个参数,那么你应该激活重构手法Replace Parameter with Method。、在这里,已有的对象可能是函数所属的类内的一个字段,也可能是另一个参数。你还可以运用Preserve Whole Object将来自同一个对象的一堆数据收集起来,并以该对象替换它们。如果某些数据缺乏合理的对象归属。可使用Introduce Parameter Object将它们制造出一个参数对象

 

 

 

Replace Parameter with Methods(以函数取代参数);

对象调用某个函数,并将所得结果作为参数,传递给另一个函数。

而接受该参数的函数本身也能够调用前一个函数

 

 

让参数接受者去除该项参数,并直接调用前一个函数

 

做法:

  1. 1.     如果有必要,将参数的计算过程提炼到一个独立函数中
  2. 2.     将函数本体内引用该参数的地方改为调用新建的函数
  3. 3.     每次替换后,修改并测试
  4. 4.     全部替换完成后,使用Remove Parameter277)将该参数去掉

 

范例:

1.

      public double getPrice(){

           int basePrice =_quantity * _itemPrice;

           int discountLevel;

          

           if(_quantity > 100){

                 discountLevel  =2 ;

           }else{

                 doscountLevel = 1;

           }

          

           double finalPrice  = discountedPrice(basePrice,discountLevel);

           return finalPrice;

      }

     

      private double discountedPrice(int basePrice ,int discountLevel){

           if(discountLevel ==2){

                 return basePrice * 0.1;

           }else{

                 return basePrice * 0.05;

           }

      }

 

 

2.提炼代码为一个独立函数,将折扣等级discountLevel

      public double getPrice(){

           int basePrice =_quantity * _itemPrice;

           int discountLevel =  getDiscountLevel();

          

           double finalPrice  = discountedPrice(basePrice,discountLevel);

           return finalPrice;

      }

     

      private int getDiscountLevel() {

           if(_quantity > 100){

                 return 2;

           }else{

                 return 1;

           }

      }

 

 

3.将函数本体内引用discountLevel的地方替换为getDiscountLevel()

      private double discountedPrice(int basePrice ,int discountLevel){

           if(getDiscountLevel() ==2){

                 return basePrice * 0.1;

           }else{

                 return basePrice * 0.05;

           }

      }

4.使用remove Parameter去掉discountLevel参数

public double getPrice(){

           int basePrice =_quantity * _itemPrice;

           int discountLevel =  getDiscountLevel();

          

           double finalPrice  = discountedPrice(basePrice);

           return finalPrice;

      }

     

      private int getDiscountLevel() {

           if(_quantity > 100){

                 return 2;

           }else{

                 return 1;

           }

      }

 

      private double discountedPrice(int basePrice){

           if(getDiscountLevel() ==2){

                 return basePrice * 0.1;

           }else{

                 return basePrice * 0.05;

           }

      }

 

5.去掉discountLevel变量

      public double getPrice(){

           int basePrice =_quantity * _itemPrice;

          

           double finalPrice  = discountedPrice(basePrice);

           return finalPrice;

      }

 

6.除去其他非必要参数

      public double getPrice(){

                 return discountedPrice();

      }

     

      private int getDiscountLevel() {

           if(_quantity > 100){

                 return 2;

           }else{

                 return 1;

           }

      }

 

      private double discountedPrice(){

           if(getDiscountLevel() ==2){

                 return getBasePrice() * 0.1;

           }else{

                 return getBasePrice() * 0.05;

           }

      }

 

      private double getBasePrice() {

           return _quantity * _itemPrice;

      }

 

 

7.使用inline Method(117)

public double getPrice(){

           if(getDiscountLevel()==2){

                 return getBasePrice() * 0.1;

           }

           else{

                 return getBasePrice() * 0.05;

           }

      }

 

 

 

Inline Method(内联函数)

一个函数的本体和名称同样清楚易懂

在函数调用点插入函数本体,然后移除该函数

1

      int getRating(){

           return (moreThanFiveLateDeliveries())?2 : 1;

      }

      boolean moreThanFiveLateDelivers(){

           return _numberOfLateDeliveries > 5;

      }

     

 

      2

      int getRating(){

           return (_numberOfLateDeliveries>5)?2:1;

      }

 

其内部代码和函数名称同样清晰易读,你就应该去掉这个函数

    

做法:

  1. 1.   检查函数,确定它不具多态性

2. 找出这个函数的所有被调用点。

3. 将这个函数的所有被调用点都替换为函数本体

4. 编译,测试

5. 删掉该函数的定义

 

 

 

 

 

 

3.6 Shotgun Surery(霰弹式修改)

如果需要修改的代码散步四处,你不但很难找到它们,也很容易忘记某个重要的修改。

这种情况下你应该使用Move Method(142)和Move Field(146)把所有需要修改的代码放进同一个类。如果眼下没有合适的类可以安置这些代码,就创造一个。通常可以运用Inline Class(154)把一系列相关的行为放进同一个类。这可能会造成少量Divergent Change(发散式变化),但你可以轻易处理它。

 

 

Inline Class(将类内联化)

某个类没有做太多的事情

将这个类的所有特性搬移到另一个类中,然后移除原类

 

做法:

1. 在目标身上声明源类的public协议,并将其中所有函数委托至源类

2. 修改所有源类的引用点,改而引用目标类

3. 编译,测试

4. 运用Move method和Move Field,将源类的特性全部搬移到目标类

5. 为源类举行一个简单的丧礼

 

 

1.

class Person{

     

     

      public String getName(){

           return _name;

      }

      public String getTelephoneNumber(){

            return _officeTelephone.getTelephoneNumber();

      }

      TelephoneNumber getOfficeTelephone(){

           return _officeTelephone;

      }

     

      private String _name;

      private TelephoneNumber _officeTelephone = new TelephoneNumber();

     

}

class TelephoneNumber{

     

      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;

      }

     

      private String _number;

      private String _areaCode;

}

 

 

 

 

2.

class Person{

     

      String getAreaCode(){

           return _officeTelephone.getAreaCode();

      }

      void setAreaCode(String arg){

           _officeTelephone.setAreaCode(arg);

      }

     

      String getNumber(){

           return _officeTelephone.getNumber();

      }

      void setNumber(String arg){

           _officeTelephone.setNumber(arg);

      }

     

      public String getName(){

           return _name;

      }

      public String getTelephoneNumber(){

           return _officeTelephone.getTelephoneNumber();

      }

      TelephoneNumber getOfficeTelephone(){

           return _officeTelephone;

      }

     

      private String _name;

      private TelephoneNumber _officeTelephone = new TelephoneNumber();

     

}

class TelephoneNumber{

     

      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;

      }

     

      private String _number;

      private String _areaCode;

}

 

 

 

3.把原来调用Telephone,转而使用Person接口

      Person martin = new Person();

           martin.getOfficeTelephone().setAreaCode("781");

          

           Person martin = new Person();

           martin.setAreaCode("781");

 

 

 

 

3.9 Primitive Obsession(基本类型偏执)

你可以运用Replace Data Value with Object(175)将原本单独存在的数据值替换为对象,

从而走出传统的洞窟,进入炙手可热的对象世界。

如果想要替换数据值是类型码,而它并不影响行为,则可以运用Replace type code with class

将它换掉,如果你有类型码和相关条件表达式,可运用Replace type code with Subclass或者Replace type code with State/Staregy 加以处理

 

如果你有一组总是放在一起的字段,可运用Extact Class。

如果你在参数列表中看到基本型数据,不妨试试Introduce Parameter Object  如果你发现自己正从数组中挑选数据,可运用Replace Array with Object

 

 

Replace Data Value with Object(以对象取代数据值);

你有一个数据项,需要与其他数据的行为一起使用才有意义。

将数据项变为对象

 

做法:

1. 为待替换数值新建一个类,在其中声明一个final 字段,其类型的源类中的待替换数值类型一样。然后在新类中加入这个字段的取值函数,再加上一个接受此字段为参数的构造函数

2. 编译

3. 将源类中的待替换数值字段的类型改为前面新建的类

4. 修改源类中该字段的取值函数,令它调用新类的取值函数

5. 如果源类构造函数中用到这个待替换字段(多半是赋值动作),我们就修改构造函数,令它改用新类的构造函数来对字段进行赋值动作

6. 修改源类中待替换字段的设值函数,令它为新类创建一个实例

7. 编译,测试

8. 现在,你有可能 需要对新类使用Change Value to Reference(179)

 

范例:

1.

class Order{

     

      public Order(String customer){

           _customer = customer;

      }

      public String getCustomer(){

           return _customer;

      }

      public void setCustomer(String arg){

           _customer = arg;

      }

      private String _customer;

}

 

 

2.

      private static int numberOfOrdersFor(Collection<E> orders ,String customer){

                 int result = 0;

                 Iterator iter = orders.iterator();

                 while(iter.hasNext()){

                      Order each = (Order)iter.next();

                      if(each.getCustomer().equals(customer)){

                            result++;

                      }

                 }

                 return result;

      }

 

 

 

3建立customer表示客户,建立final字段,保存字符串,取名name

表示客户名称,为这个字符串加上取值函数和构造函数

class Customer{

      public Customer(String name){

           _name = name;

      }

      public String getName(){

           return _name;

      }

      private final String _name;

}

 

 

4.

class Order{

     

      public Order(String customer){

           _customer = new Customer(customer);

      }

      public String getCustomer(){

           return _customer.getName();

      }

      public void setCustomer(String arg){

           _customer = new Customer(arg);

      }

      private Customer  _customer;

}

 

 

 

5.对于取值函数修改其名称

      public String getCustomerName(){

           return _customer.getName();

      }

 

 

6.函数名称不用修改,参数值修改

      public Order(String customerName){

           _customer = new Customer(customerName);

      }

      public void setCustomer(String customerName){

           _customer = new Customer(customerName);

      }

 

 

Change Value to Reference(将值对象改为引用对象)

你从一个类衍生出许多彼此相等的实例,希望将它们替换为同一个对象

将这个值对象变成引用对象

 

做法:

  1. 1.     使用Replace Constructor with Factory Method304
  2. 2.     编译测试
  3. 3.     决定由什么对象负责提供访问新对象的途径
  4. 4.     决定这些引用对象应该预先创建好,或是应该动态创建
  5. 5.     修改工厂函数,令它返回引用对象
  6. 6.     编译测试

 

 

范例:

1.

class Customer{

      public Customer(String name){

           _name = name;

      }

      public String getName(){

           return _name;

      }

      private final String _name;

}

 

 

2、

class Order{

     

      public String getCustomerName(){

           return _customer.getName();

      }

      public Order(String customerName){

           _customer = new Customer(customerName);

      }

      public void setCustomer(String customerName){

           _customer = new Customer(customerName);

      }

      private Customer  _customer;

}

 

4.

      private static int numberOfOrdersFor(Collection<E> orders ,String customer){

                 int result = 0;

                 Iterator iter = orders.iterator();

                 while(iter.hasNext()){

                      Order each = (Order)iter.next();

                      if(each.getCustomerName().equals(customer)){

                            result++;

                      }

                 }

                 return result;

}

 

 

5.到目前位置,Customer对象还是值对象,设置工厂函数

class Customer{

     

      public static Customer create(String name){

           return new Customer(name);

}

 

6.把构造函数 改为调用工厂

class Order{

     

      public String getCustomerName(){

           return _customer.getName();

      }

      public Order(String customerName){

           _customer = Customer.create(customerName);

}

 

 

7.把构造函数声明为private

class Customer{

     

      private  Customer(String name){

           _name = name;

}

 

 

 

 

 

  1. 本例没有一个明显的字段可用于访问Customer对象,

在这种情况下,我通常会创建一个注册表对象来保存所有Customer对象,

以此作为访问点,我把这个注册表保存在Customer类的static字段

创建注册表对象,用于保存所有Customer对象

private static Dictionary _instances = new Hashtable();

 

9.

class Customer{

     

      static void loadCustomers(){

           new Customer("Lemon Car Hire").store();

           new Customer("Assocaited Coffee Machines").store();

           new Customer("Bilston Gasworks").store();

      }

     

      private void store(){

           _instance.put(this.getName(),this);

      }

 

 

10.修改工厂函数,返回预先创建好的Customer对象

      public static Customer create(String name){

           return (Customer)_instances.get(name);

}

 

11. 修改工厂函数,令它返回引用对象。如果对象是预先创建的,你需要考虑:万一有人索求一个其实并不存在的对象,要如何处理?你可能希望对工厂函数使用Rename Method (函数改名),使其传达这样的信息:它返回的是一个既存对象。sssssssssss

      public static Customer getNamed(String name){

           return (Customer)_instances.get(name);

}

 

 

Replace Array with Object(以对象取代数组)

 

你有一个数组,其中的元素各自代表不同的东西

以对象替换数组,对于数组中的每个元素,以一个字段来表示

 

1

String[] row = new String[3];

           row[0] = "Liveing";

           row[1] = "15";

          

 

2

           Performance row = new Performance();

           row.setName("liveing");

           row.setWins("15");

 

 

做法:

1. 新建一个类表示数组所拥有的信息,并在其中以一个public字段保存原先的数组

2. 修改数组的所有用户,让它们改用新类的实例

3. 编译测试

4. 逐一为数组元素添加取值/设值函数。根据元素的用途,为这些访问函数命名。修改客户端代码,让它们通过访问函数取出数组内的元素,每次修改后,编译测试

5. 当所有对数组的直接访问都转而调用访问函数后,将新类中保存该数组的字段声明为private

6. 编译

7. 对于数组内的每一个元素,在新类中创建一个类型相当的字段。修改该元素的访问函数,令它改用上述的新建字段

8. 每修改一个元素,编译测试

9. 数组的所有元素都有了相应字段之后,删除该数组

 

 

 

 

范例:

1

             String[] row = new String[3];

           row[0] = "Liveing";

           row[1] = "15";

          

           String name = row[0];

           int wins = Integer.parseInt(row[1]);

 

 

2.建立对应的类

class Performance{

     

}

3.声明public字段 保存原先的数组

class Performance{

     

            public String[] _data = new String[3];

     

}

  1. 找到创建和访问数组的地方替换代码

Performance row = new Performance();

 

 

  1. 对数组的使用地点,替换
    row._data[0] = "liveing";

           row._data[1] = "15";

           String name = row._data[0];

      int wins = Integer.parseInt(row._data[1]);

 

 

7.为数组加上取值函数和设值函数

class Performance{

     

      public String[] _data = new String[3];

     

      public void setName(String arg){

           _data[0] = arg;

      }

      public String getName(){

           return _data[0];

      }

     

      public void setWins(String arg){

           _data[1] = arg;

      }

      public int getWins(){

           return Integer.parseInt(_data[1]);

      }

}

 

 

8.改用函数访问名称

           row.setName("liveing");

           row.setWins("15");

          

           String name = row.getName();

      int wins  = row.getWins();

 

 

9.针对数组的元素,在Performance建立一个类型相当的字段,然后修改访问元素的访问函数

class Performance{

     

      public void setName(String arg){

           _name = arg;

      }

      public String getName(){

           return _name;

      }

     

private String _name;

 

10.针对数组的所有元素改变,然后删掉数组

class Performance{

     

      public void setName(String arg){

           _name = arg;

      }

      public String getName(){

           return _name;

      }

     

      public void setWins(String arg){

           _wins = arg;

      }

      public int getWins(){

           return Integer.parseInt(_wins);

      }

      private String _name;

      private String _wins;

}

 

 

 

Replace Type Code with Class(以类取代类型码)

类之中有一个数值类型码,但它并不影响类的行为

以一个新的类替换该数值类型码

 

 

 

 

做法:

  1. 1.     为类型码建立一个类
  2. 2.     修改源类实现,让它使用上述新建的类
  3. 3.     编译测试
  4. 4.     对于源类中每一个使用类型码的函数,相应建立一个函数,让新函数使用新建的类
  5. 5.     逐一修改源类用户,让它们使用新接口
  6. 6.     每修改一个用户,编译并测试
  7. 7.     删除使用类型码的旧接口,并删除保存旧类型码的静态变量
  8. 8.     编译测试

范例:

1.类型码表示血型

class Person{

     

      public static final int o = 0;

      public static final int A = 1;

      public static final int B = 2;

      public static final int AB = 3;

     

      private int _bloodGroup;

     

      public Person(int bloodGroup){

           _bloodGroup = bloodGroup;

      }

     

      public void setBloodGroup(int arg){

           _bloodGroup =arg ;

      }

     

      public int getBloodGroup(){

           return _bloodGroup;

      }

}

 

 

2.建立新类,用于表示原本的类型码血型

class BloodGroup{

     

      public static final BloodGroup O = new BloodGroup(0);

      public static final BloodGroup A = new BloodGroup(1);

      public static final BloodGroup B = new BloodGroup(2);

      public static final BloodGroup AB = new BloodGroup(3);

     

      private static final BloodGroup[] _values = {O,A,B,AB};

     

      private final int _code;

     

      private BloodGroup(int code){

           _code = code;

      }

     

      public int getCode(){

           return _code;

      }

     

      public static BloodGroup code(int arg){

           return _values[arg];

      }

     

}

 

 

3.修改Person的类型码

class Person{

     

      public static final int O = BloodGroup.O.getCode();

      public static final int A = BloodGroup.A.getCode();

      public static final int B =BloodGroup.B.getCode();

      public static final int AB = BloodGroup.AB.getCode();

     

      private BloodGroup _bloodGroup;

     

      public Person(int bloodGroup){

           _bloodGroup = BloodGroup.code(bloodGroup);

      }

     

      public void setBloodGroup(int arg){

           _bloodGroup = BloodGroup.code(arg);

      }

     

      public int getBloodGroupCode(){

           return _bloodGroup.getCode();

      }

}

 

 

4.使用Rename Method(273)修改类型码访问函数的名称

Class Person{

      public int getBloodGroupCode(){

           return _bloodGroup.getCode();

}

 

  1. Person中加入取值函数

      public BloodGroup getBloodGroup(){

           return _bloodGroup;

}

 

 

7.Person中加入新的构造函数和 设值函数

      public Person(BloodGroup bloodGroup){

           _bloodGroup = bloodGroup;

      }

     

      public void setBloodGroup(BloodGroup arg){

           _bloodGroup = arg;

      }

 

 

8.对Person内的静态变量的所有引用点也需要修改

 

      Person thePerson = new Person(Person.A);

          

      Person thePerson  = new Person(BloodGroup.A);

 

 

9.修改取值函数和设值函数

           thePerson.getBloodGroupCode();

      thePerson.getBloodGroup().getCode();

 

            thePerson.setBloodGroup(Person.AB);

      thePerson.setBloodGroup(BloodGroup.AB);

 

 

10.删掉原本的整形取值函数,设值函数,构造函数,静态变量

class Person{

     

      private BloodGroup _bloodGroup;

 

      public Person(BloodGroup bloodGroup){

           _bloodGroup = bloodGroup;

      }

     

      public void setBloodGroup(int arg){

           _bloodGroup = BloodGroup.code(arg);

      }

     

      public int getBloodGroupCode(){

          

           return _bloodGroup.getCode();

      }

     

      public BloodGroup getBloodGroup(){

           return _bloodGroup;

      }

}

11.将BloodGroup的函数声明为private,没人在理会它们

      private int getCode(){

           return _code;

      }

     

      private static BloodGroup code(int arg){

           return _values[arg];

}

 

 

 

3.10 Switch Statements(switch 惊悚现身)

面向对象程序的一个明显特征是:少用switch(或case)语句

Switch过于重复。

你要的是与该类型码相关的函数和类,所以应该使用Extract Method将switch语句提炼到一个独立函数中,再以move method将它搬移到需要多态性的那个类里,此时你必须决定是否使用Replace Type Code with Subclass 或 Replace Type Code witch state/Strategy

一旦这样完成继承结构之后,你就可以运用Replace Conditional with polymorphism

 

如果你只是单一函数中有些选择事例,并不想改动,

这种情况下Replace Parameter with Explicit Methods,如果你的选择条件之一是null,可以试试Introduce Null Object

 

 

Replace Parameter with Explicit Methods(以明确函数取代参数)

 

你有一个函数,其中完全取决于参数值而采取不同的行为

针对该参数的每一个可能值,建立一个独立函数

 

1

void setValue(String name,int value){

           if(name.equals("height")){

                 _height = value;

                 return;

           }

           if(name.equals("width")){

                 _width = value;

                 return;

           }

           Assert.shouldNeverReachHere();

      }

     

 

2.

void setHeight(int arg){

           _height = arg;

      }

      void setWidth(int arg){

           _width = arg;

      }

 

如果某个参数有多种可能的值,而函数内又以条件表达式检查这些参数值,并根据不同的参数值做出不同的行为

 

做法:

1. 针对参数的每一种可能值,新建一个明确函数

2. 修改条件表达式的每个分支,使其调用合适的新函数

3. 修改每个分支后,编译并测试

4. 修改原函数的每一个被调用点,改而调用上述的某个合适的新函数

5. 编译测试

6. 所有调用端都修改完毕后,删除原函数

 

 

1.

class Employee{

     

      static final int ENGINERR = 0;

      static final int SALESMAN = 1;

      static final  int MANAGER = 2;

     

      static Employee create(int type){

          

           switch(type){

                 case ENGINERR:

                      return new Enginerr();

                 case SALESMAN:

                      return new Salesman();

                 case MANAGER:

                      return new Manager();

                 default:

                            throw new IllegalArgumentException("Incorrect type code value");

           }

      }

}

 

 

2.根据参数值建立相应函数

      static Employee createEngineer(){

           return new Engineer();

      }

     

      static Employee createSalesman(){

           return new Salesman();

      }

     

      static Employee createManager(){

           return new Manager();

      }

     

 

3. 各个分支调用新函数

      static Employee create(int type){

          

           switch(type){

                 case ENGINERR:

                      return Employee.createEngineer();

                 case SALESMAN:

                      return  Employee.createSalesman();

                 case MANAGER:

                      return  Employee.createManager();

                 default:

                            throw new IllegalArgumentException("Incorrect type code value");

           }

      }

 

 

4.修改函数的调用者,就可以把create()函数删掉了,同时也可以把所有的常量删掉了

Employee kent = Employee.create(ENGINEER);

          

           Employee kent =Employee.createEngineer();

 

 

 

 

Introduce Null Object(引入NULL对象);

 

      if(customer==null){

                 plan = BillingPlan.basic();

           }else{

                 plan = customer.getPlan();

           }

 

做法:

1. 为源类建立一个子类,使其行为就像是源类的null版本,

在源类和null子类中都加上isNul()l函数,前者返回false,后者返回true

2. 编译

3. 找出所有索求源对象却获得一个null的地方,修改这些地方,使它们改而获得一个空对象

4. 找出所有将源对象与null做比较的地方。修改这些地方,使他们调用isNull函数

5. 编译测试

6. 找出这样的程序点,如果对象不是null,做A动作,否则做B动作

7. 对于每一个上述地点,在null类中覆写A动作,使其行为和B动作相同

8. 使用上述被覆写的动作,然后删除对象是否等于null的条件测试。编译测试

 

 

范例:

 

1.

Site场所 Customer 顾客 PaymentHistory顾客的付款记录

class Site{

      Customer getCustomer(){

           return _customer;

      }

     

      Customer _customer;

}

class Customer{

     

      public String getName(){

          

      }

      public BillingPlan getPlan(){

          

      }

      public PaymentHistory getHistory(){

          

      }

}

class PaymentHistory{

     

      int getWeeksDelinquentInLastYear()

}

 

 

 

 

 

3

           String customerName;

           if(customer==null){

                 customerName = "occupant";

           }else{

                 customerName = customer.getName();

           }

          

           int weeksDelinquent;

           if(customer==null){

                 weeksDelinquent = 0;

           }else{

                 weeksDelinquent = customer.getHistory().getWeeksDelinquentInLastYear();

            }

           }

 

 

 

4.

class NullCustomer extends Customer{

      public boolean isNull(){

           return true;

      }

}

 

class Customer{

     

      public boolean isNull(){

           return false;

      }

     

      protected Customer(){

          

      }

}

 

 

  1. 如果喜欢可以新建接口

interface Nullable{

      boolean isNull();

}

class Customer implements Nullable

 

7.使用静态工厂,用户不必知道空对象的存在,创建空对象

class Customer{

     

      static Customer newNull(){

           return new NullCustomer();

      }

}

 

 

8.查找返回Customer对象的地方,让他们不返回null,返回一个NullCustomer空对象

class Site{

      Customer getCustomer(){

           return (_customer==null)?Customer.newNull():_customer;

      }

     

      Customer _customer;

}

 

 

9.不再使用==null检查,而是使用isNull()

           Customer customer = site.getCustomer();

           BillingPlan plan;

           if(customer.isNull()){

                 plan = BillingPlan.basic();

           }else{

                 plan = customer.getPlan();

           }

          

           String customerName;

           if(customer.isNull()){

                 customerName = "occupant";

           }else{

                 customerName = customer.getName();

           }

          

           int weeksDelinquent;

           if(customer.isNull()){

                 weeksDelinquent = 0;

           }else{

                 weeksDelinquent = customer.getHistory().getWeeksDelinquentInLastYear();

           }

           }

 

 

 

10.

class NullCustomer extends Customer{

      public String getName(){

           return "occupant";

      }

}

 

10. 去掉条件代码

 

      String customerName = customer.getName();

          

 

 

           if(!customer.isNull()){

                 customer.setPlan(BillingPlan.special());

      }         

 

变成这样

customer.setPlan(BillingPlan.special());

          

           class NullCustomer{

                 public void setPlan(BillingPlan arg){}

      }

 

 

 

11.新建NullPaymentHistory

      if(customer.isNull()){

                 weeksD     elinquent = 0;

           }else{

                 weeksDelinquent = customer.getHistory().getWeeksDelinquentInLastYear();

      }

 

11. 修改NullCustomer,让他返回一个NullPaymentHistory类

class NullCustomer{

      public PaymentHistory getHistory{

return PaymentHistory.newNull();

}

}

 

12.同样可以删除这一行代码

int weeksDelinquent = customer.getHistory().getWeeksDelinquentInLastYear();

 

 

 

 

 

3.12 冗赘(zhui)类

 

某个类原本对得起自身的身价,但重构使它身形缩水,不再做那么多工作,或开发者事前规划了某些变化,并添加了一个类应付这些变化,但变化实际并没有发生,如果子类没有足够的工作,使用Collapse Hierarchy,对于几乎没用的组件使用Inline Class

 

 

Collapse Hierarchy (折叠继承体系)

超类和子类之间无太大区别

将它们合为一体

 

某个子类并未带来该有的价值,因此需要把超类与子类合并起来

 

做法:

1. 选择你想要移除的类,是超类还是子类

2. 使用PULL UP FIELD和PULL UP METHOD或者PUSH DOWN METHOD和PUSH DOWN FIELD,把想要移除的类的所有行为和数据搬移到另一个类

3. 每次移动后,编译并测试

4. 调整即将被移除的那个类的所有引用点,令它们改而引用合并后留下的类,这个动作将会影响变量的声明,参数的类型以及构造函数

5. 移除我们的目标,此时的它应该已经成为一个空类

6. 编译,测试

 

 

3.13. Speculative Generality(夸夸其谈未来性)

如果你的某个抽象类其实没有太大作用,请运用Collapse Hierarchy。不必要的委托可运用Inline Class除掉,如果函数的某些参数未被用上,可对它实施Remove Parameter 如果函数名称带来多余的抽象意味,应该对它实施Rename Method,让它现实一些

Remove Parameter(移除参数)

函数的本体不再需要某个参数

将该参数去除

参数代表函数所需要的信息,不同的参数值有不同的意义

如果你去掉多余的参数,就是让你的每一位用户多费一份心

 

 

 

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

如果你看到用户向一个对象请求另一个对象,然后再向后者请求另一个对象,然后再请求另一个对象,,,这就是消息链,结构紧密结合,一旦对象间的关系发生变化,客户端就不得不做出相应修改,这时候你应该使用Hide Delegate

 

Hide Delegate(隐藏“委托关系”)

客户通过一个委托类来调用另一个对象。

在服务类上建立客户所需的所有函数,用以隐藏委托关系

 

 

 

可以在服务对象上放置一个简单的委托函数,将委托关系隐藏起来,从而去除这种依赖,这么一来,即便将来发生委托关系上的变化,变化也将被限制在服务对象中,不会涉及客户

 

做法:

1. 对于每一个委托关系中的函数,在服务对象端建立一个简单的委托函数

2. 调整客户,令它只调用服务对象提供的函数

3. 每次调整后,编译并测试

4. 如果将来不再有任何客户需要取用Delegate受拖类,便可移除服务对象中的相关访问函数

5. 编译测试

 

范例:“

1 Person 人 Department 部门

class Person{

      Department _departement;

     

      public Department getDepartment(){

           return _department;

      }

     

      public void sertDepartment(Department arg){

           _department = arg

      }

}

class Department{

      private String _chargeCode;

      private Person _manager;

     

      public Department(Person manager){

           _manager = manager;

      }

     

      public Person getManager(){

           return _manager;

      }

}

 

 

2 如果想知道经理是谁必须先取得Department

           manager = join.getDepartment().getManager();

 

3. 这样编码揭露了department的工作原理,

如果对客户隐藏department,可以减少耦合

修改person的函数

public Person getManager(){

           return _department.getManager();

      }

     

 

4

           manager = join.getManager();

 

只要完成了对Department的所有函数的委托关系,并相应的修改了Person的所有客户,我就可以移除Person中的访问函数getDepartment()

 

 

 

3.16 Middle Man(中间人)

但是人们可能过度运用委托,也许会看到某个类接口只有一半的函数都委托给其他类,这样就是过度运用,这时应该使用Remove Middle Man,直接和真正负责的对象打交道。

如果这样不干实事的函数只有少数几个,可以运用InlineMethod把它们放进调用端,如果这些Middle Man还有其他行为,可以运用Replace Delegation with Inheritance把它变成实责对象的子类,这样你既可以扩展原对象的行为,又不必担那么多的委托动作

 

 

 

Remove Middle Man(移除中间人)

某个类做了过多的简单委托动作

让客户直接调用受托类

 

做法:

  1. 1.     建立一个函数,用以获得受托对象
  2. 2.     对于每个受托函数,在服务类中删除该函数,并让需要调用该函数的客户转为调用受托对象
  3. 3.     处理每个受托函数后,编译,测试

 

class Person{

     

      Department1 _department;

     

      public Person getManager(){

           return _department.getManager();

      }

}

class Department1{

      private Person _manager;

      public  Department1(Person manager){

           _manager = manager;

      }

}

 

manager = join.getManager();

 

2.这样的封装不得不在Person中安置大量的委托行为

首先在Person中获得受托对象

 

      public Department getDepartment(){

           return _department;

      }

 

 

3.使它首先获得受托对象,然后直接使用后者

manager = join.getDeparment().getManager();

 

4.就可以移除Person中的getManager()函数了

 

 

Replace Delegation with Inheritance(以继承取代委托)

你在两个类之间使用委托关系,并经常为整个接口编写许多极简单的委托函数

让委托类继承受托类

 

 

 

做法:

1. 让委托端成为受托端的一个子类

2. 编译

3. 将受托字段设为该字段所处对象本身

4. 去掉简单的委托函数

5. 编译测试

6. 将所有其他涉及受托关系的代码。改为调用对象本身

7. 移除受托字段

 

 

1.

class Employee{

     

      Person  _person = new Person();

     

      public String getName(){

           return _person.getName();

      }

     

      public void setName(String arg){

           return_person.setName(arg);

      }

     

      public String toString(){

           return "Emp: "+_person.getLastName();

      }

}

class Person{

     

      String _name;

     

      public String getName(){

           return _name;

      }

      public void setName(String arg){

           _name = arg;

      }

      public String getLastName(){

           return _name.substring(_name.lastIndexOf(" ")+1);

      }

}

 

 

2.声明两者的继承关系

class Employee extends Person

 

 

  1. 下一步将受托字段设值为该字段所处对象自身

同时删掉简单委托函数

 

 

class Employee extends Person{

     

      public String toString(){

           return "Emp: "+getLastName();

      }

}

 

 

 

3.17 Inappropriate Intimacy(狎昵关系)

就像古代恋人一样,过分狎昵的类必须拆散,你可以采用Move method 和 move Field帮助它们划清界限,从而减少狎昵的行为,你也可以看看是否可以运用Change bidirectional association to unidirectional让其中一个类对另一个类斩断情丝。如果两个类情投意合,可以运用Extract class把两者的共同点提炼到一个安全的地方,让它们坦荡地使用这个类或者也可以尝试hide Delegate让另一个类来为它们传递相思情

 

 

继承往往造成过度亲密,如果你觉得该让孩子独立生活了,请运用Replace Inheritance with Delegation

 

Change Bidirectional Association to Unidirectional(将双向关联改为单向关联)

 

两个类之间有双向关联,但其中一个类如今不再需要另一个类的特性,去除不必要的关联

 

做法:

1. 找出保存你想去除的指针的字段,检查它的每一个用户,判断是否可以去除该指针

2. 如果客户使用了取值函数,先运用self Encapsulate Field将待删除字段自我封装起来,然后使用Substitute Algorithm对付取值函数,令他不再使用该字段,然后编译,测试

3. 如果客户并未使用取值函数,那就直接修改待删除字段的所有被引用点,改以其他途径获得该字段所保存的对象,每次修改后,编译

4. 如果已经没有任何函数使用待删除字段,移除所有对该字段的更新逻辑,然后移除该字段

 


 

1.Order和Customer有双向关联

class Order{

     

      Customer getCustomer(){

           return _customer;

      }

      void setCustomer(Customer arg){

           if(_customer!=null){

                 _customer.friendOrders().remove(this);

           }

           _customer = arg;

           if(_customer!=null){

                 _customer.friendOrders().add(this);

           }

      }

      private Customer _customer;

}

class Customer{

      void addOrder(Order arg){

           arg.setCustomer(this);

      }

      private Set _orders = new HashSet();

      Set frientOrders(){

           return _orders;

      }

}

 

 

 

2.除非先有Customer对象,否侧不会存在Order对象

问题在于是否有任何代码依赖_customer字段存在

如果确实有,那么在删除这个字段后,必须提供替代品

 

class Order{

     

      double getDiscountedPrice(){

           return getGrossPrice() * (1-_customer.getDiscount());

}

 

改为

class Order{         

      double getDiscountedPrice(Customer customer){

                 return getGrossPrice() * (1-_customer.getDiscount());

      }

 

 

 

3.

class Customer{

     

      double getPriceFor(Order order){

           return order.getDiscountedPrice();

      }

      变成了

      double getPriceFor(Order order){

           return order.getDiscountedPrice(this);

      }

 

 

 

 

Replace Inheritance with Delegation(以委托取代继承)

某个子类只使用超类接口中的一部分,或是根本不需要继承而来的数据。

在子类中新建一个字段用以保存超类,调整子类函数

令它改而委托超类,然后去掉两者之间的继承关系

 

 

做法:

1. 在子类中新建一个字段,使其引用超类的一个实例,并将他初始化为this

2. 修改子类内的所有函数,让它们不再使用超类,转而使用上述那个受托字段,每次修改后,编译并测试

3. 去除两个类之间的继承关系,新建一个受托类的对象赋给受托字段

4. 针对客户端所有的每一个超类函数,为它添加一个简单的委托函数

5. 编译,测试

 

 

1

class MyStack extends Vector{

     

      public void push(Object element){

           insertElement(element,0);

      }

      public Object pop(){

           Object result = firstElement();

           removeElementAt(0);

           return result;

      }

}

 

2.我要在Mystack新建一个字段,用以保存受托的VECTOR对象

class MyStack extends Vector{

     

      private Vector _vector = this;

 

3.修改mysttack函数 ,让它们使用委托关系

class MyStack extends Vector{

     

      private Vector _vector = this;

     

      public void push(Object element){

           _vector.insertElementAt(element,0);

      }

      public Object pop(){

           Object result = _vector.firstElement();

           _vector.removeElementAt(0);

           return result;

      }

}

 

4.打破与超类之间的关系

class MyStack extends Vector{

     

      private Vector _vector = new Vector();

 

 

5.

class MyStack extends Vector{

     

      private Vector _vector = new Vector();

     

      public void push(Object element){

           _vector.insertElementAt(element,0);

      }

      public Object pop(){

           Object result = _vector.firstElement();

           _vector.removeElementAt(0);

           return result;

      }

     

      public int size(){

           return _vector.size();

      }

      public boolean isEmpty(){

           return _vector.isEmpty();

      }

}

     

 

 

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

如果两个函数做同一件事,却有着不同的签名,请运用Rename method根据它们的用途重新命名

但这往往还不够,反复运用move method 将某些行为移人类

直到两者协议一致为止

Rename Method(函数改名)

函数名称未能提示函数的用途

修改函数名称

 

做法:

1. 检查函数签名是否被超类或子类实现过。

如果是,则需要针对每份实现分别进行下列步骤。

2声明一个新函数,将它命名为你想要的新名称,将旧函数的代码复制到新函数中,并进行适当调整。

3编译

4修改旧函数,令它将调用转发给新函数

6. 编译测试

7. 找出旧函数的所有被引用点,修改它们。令它们改而引用新函数,每次修改后,编译并测试

8. 删除旧函数

9. 编译,测试

 

范例:

      public String getTelephoneNumber(){

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

      }

 

 

2.复制代码,调用新函数

class Person{

 

      public String getOfficeTelephoneNumber(){

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

      }

     

      public String getTelephoneNumber(){

           getOfficeTelephoneNumber();

      }

}

 

  1. 找到所有旧函数的调用点,将它们全部改为调用新函数,全部修改完后,就可以将旧函数删掉

 

 

 

3.19 Incomplete Library Class(不完美的库类)

如果你只想修改库类的一两个函数,可以运用Introduce Foreign method,

如果你想添加一大堆额外行为,就得运用Introduce Local Extension

Introduce Foreign Method(引入外加函数)

你需要为提供服务的类增加一个函数,但你无法修改这个类

在客户类中建立一个函数,并以第一参数形式传入一个服务类实例

 

你正在使用一个类,它真的很好,为你提供了需要的所有服务,

如果可以修改源码,你便可以自行添加一个新函数,如果不能,你就得在客户端编码,补足你要的那个函数

如果你需要多次使用这个函数,就得不断重复这些代码

 

 

做法:

1、             在客户类中建立一个函数,用来提供你需要的功能

2、             以服务类实例作为该函数的第一个参数

3、             将该函数注释为:外加函数,应在服务类实现

4、      

5、            Date newStart = new Date(previousEnd.getYear(),previousEnd.getMonth(),previousEnd.getDate()+1);

6、           

7、            Date newStart = nextDay(previousEnd);

8、       }

9、       private static Date nextDay(Date arg){

10、                   return new Date(arg.getYear(),arg.getMonth(),arg.getDate()+1);

11、              }

 

 

Introduce Local Extension(引入本地扩展)

你需要为服务类提供一些额外函数,但你无法修改这个类

建立一个新类,使它包含这些额外函数,让这个类扩展品为源类的子类或包装类

 

 

做法:

  1. 1.     建立一个扩展类,将它作为原始类的子类或包装类
  2. 2.     在扩展类中加入转型构造函数
  3. 3.     在扩展类中加入新特性
  4. 4.     根据需求,将原对象替换为扩展对象
  5. 5.     将针对原始类定义的所有外加函数搬移到扩展类中

 

范例:

子类

class MfDateSub extends Date{

     

      public MfDateSub nextDay(){}

      public int dayOfYear(){}

}

包装类:

class MfDateWrap{

      private Date _original;

}

 

 

 

 

1.

class MfDateSub extends Date{

2

//mfDateSub的构造函数委托给Date

public MyDateSub(String dateString){

      super(dateString);

}

3.

 

//加入转型构造函数

public MfDateSub(Date arg){

      super(arg.getTime());

}

 

4.

Date nextDay(){

      return new Date(getYear(),getMonth(),getDate()-1);

}

 

class MfDateSub extends Date{

 

//mfDateSub的构造函数委托给Date

public MyDateSub(String dateString){

      super(dateString);

}

 

//加入转型构造函数

public MfDateSub(Date arg){

      super(arg.getTime());

}

 

Date nextDay(){

      return new Date(getYear(),getMonth(),getDate()-1);

}

}

 

 

 

 

1、

class MfDateWrap{

      private Date _original;    

 

      //将构造函数做一个委托动作

      public MfDateWrap(String dateString){

           _original = new Date(dateString);

      }

     

      //转型构造函数是对实例变量的赋值

      public MyDateWrap(Date arg){

           _original = arg;

      }

     

      //为原始类提供委托函数

      public int getYear(){

           return _original.getYear();

      }

     

      public boolean equals(Object arg){

           if(this==arg){

                 return true;

           }

           if(!(arg instanceof MfDateWrap)){

                 return false;

           }

           MfDateWrap other = ((MfDateWrap)arg);

           return (_original.equals(other._original));

      }

 

 

2

class MfDateWrap{

     

      private Date _original;

     

      //将构造函数做一个委托动作

      public MfDateWrap(String dateString){

           _original = new Date(dateString);

      }

     

      //转型构造函数是对实例变量的赋值

      public MyDateWrap(Date arg){

           _original = arg;

      }

     

      //为原始类提供委托函数

      public int getYear(){

           return _original.getYear();

      }

     

      public boolean equals(Object arg){

           if(this==arg){

                 return true;

           }

           if(!(arg instanceof MfDateWrap)){

                 return false;

           }

           MfDateWrap other = ((MfDateWrap)arg);

           return (_original.equals(other._original));

      }

     

      //搬移函数到新类中

      Date nextDay(){

           return new Date(getYear(),getMonth(),getDate()-1);

      }

}

 

 

 

 

3.20  Data Class(纯稚的数据类)

所谓Data class 是指:它们拥有一些字段,以及用于访问读写这些字段的函数,除此之外一无长物。这样的类只是一种不会说话的数据容器,它们几乎一定被其他过分细长琐碎地操控着,这些类早期可能拥有public 字段,果真如此你应该在别人注意到之前,立刻运用Encapsulate Field把它们封装起来,如果这些类内含有容器类的字段,你应该检查它们是不是得到了恰当的封装,,如果没有,就运用Encapsulate Collection把它们封装起来,对于那些不该被其他类修改的字段,请运用Remove Setting Method

然后找出这些取值设值函数被其他类运用的地点,尝试以Move Method把那些调用行为搬移到Data Class 来,如果无法搬移整个函数,就运用Extract Method产生一个搬移结果的函数,不久之后你就可以运用Hide Method把这些取值/设值隐藏起来

Encapsulate Field(封装字段)

你的类中存在一个public 字段

将它声明为private 并提供相应的访问函数

1

      public  String _name;

     

2、

      private String _name;

      public String getName(){

           return _name;

      }

      public void setName(String arg){

           _name = arg;

      }

 

做法:

1、        为public 字段提供取值和设值函数

2、         找到这个类以外使用该字段的所有地点,如果客户只是读取该字段,就把引用替换为对取值函数的调用,如果客户修改了该字段值,就将此引用点替换为设值函数的调用

3、         每次修改后,编译测试

4、         将字段的所有用户修改完毕后,把字段声明为private

5、         编译,测试

 

 

 

Encapsulate Collection(封装集合)

有个函数返回一个集合

让这个函数返回该集合的一个只读副本,并在这个类中提供添加/移除集合元素的函数

 

做法:

1. 加入为集合添加/移除元素的函数

2. 将保存集合的字段初始化为一个空集合

3. 编译

4. 找出集合设值函数的所有调用者,你可以修改那个设值函数,让它使用上述新建的”添加/移除元素”函数,也可以直接修改调用端,改让它们调用上述新建立的“添加/移除元素”函数

5. 编译测试

6. 找出所有“通过取值函数获得集合并修改其内容”的函数,

逐一修改这些函数,让它们改用添加/移除函数。每次修改后,编译并测试

7. 修改完上述所有“通过取值函数获得集合并修改集合内容”的函数后,修改修改函数自身,使它返回该集合的一个只读副本

8. 编译,测试

9. 找出取值函数的所有用户,从中找出应该存在于集合所属对象内的代码,运用Extract Method和Move Method将这些代码移到宿主对象去

  1. 10.    修改现有取值函数的名字,然后添加一个新的取值函数,使其返回一个枚举,找出旧取值函数的所有被使用点,将它们都改为使用新取值函数
  2. 11.    如果这一步跨度太大,你可以先使用Rename Method 修改原取值函数的名称,再建立一个新取值函数用以返回枚举,最后再修改所有调用者,使其调用新取值 函数
  3. 12.    编译测试

 

范例:

1.

//课程

class Course{

      public Course(String name,boolean isAdvanced){

          

      }

      public boolean isAdvanced(){

          

      }

     

}

 

2.

//人

class Person{

     

      public Set getCourses(){

           return _courses;

      }

      public void setCourses(Set arg){

           _courses = arg;

      }

     

      private Set _courses;

}

 

 

3.为某人添加课程

      Person kent = new Person();

           Set s = new HashSet();

           s.add(new Course("Smalltalk Programming ",false));

           s.add(new Course("Appreciating Single Malts",true));

           kent.setCourses(s);

           Assert.equals(2,kent.getCourses().size());

           Course refact = new Course("Refactoring",true);

           kent.getCourses().add(refact);

           kent.getCourses().add(new Course("Brutal Sarcasm"),false);

           Assert.equals(4,kent.getCourses().size());

           kent.getCourses().remove(refact);

           Assert.equals(3,kent.getCourses().size());

 

 

4.了解高级课程

      Iterator iter = person.getCourses().iterator();

           int count = 0;

           while(iter.hasNext()){

                 Course each = (Course)iter.next();

                 if(each.isAdvanced()){

                      count++;

                 }

           }

 

  1. 在Person中建立添加和删除函数

class Person{

     

      public void addCourse(Course arg){

           _courses.add(arg);

      }

      public Course removeCourse(Course arg){

           return _courses.remove(arg);

      }

7.初始化_courses字段    

private Set _courses = new HashSet();

 

7. 需要修改设值函数,令他调用添加移除函数

它被用来初始化集合,换句话说,设值函数被调用之前,_courses是个空集合

      public void setCourses(Set arg){

           Assert.equals(_courses.isEmpty());

           Iterator iter = arg.iterator();

           while(iter.hasNext()){

                 addCourse((Course)iter.next());

           }

}

 

8.

      public void initializeCourses(Set arg){

           Assert.equals(_courses.isEmpty());

           Iterator iter = arg.iterator();

           while(iter.hasNext()){

                 addCourse((Course)iter.next());

           }

}

 

 

9.直接使用添加移除函数,并将设值函数完全移除

      Person kent = new Person();

           Set s = new HashSet();

           s.add(new Course("Smalltalk Programming ",false));

           s.add(new Course("Appreciating Single Malts",true));

      kent.initializeCourses(s);

变成了:

      Person kent = new Person();

           Set s = new HashSet();

           kent.addCourse(new Course("Smalltalk Programming ",false));

           kent.addCourse(new Course("Appreciating Single Malts",true));

      kent.initializeCourses(s);

 

 

10.

首先处理通过取值函数修改集合元素的情况

      kent.getCourses().add(new Course("Brutal Sarcasm"),false);

修改为

      kent.addCourse(new Course("Brutal Sarcasm"),false);

 

11.让取值函数返回一个只读副本,没有任何用户能够修改集合

      public Set getCourses(){

           return Collections.unmodifiableSet(_courses);

}

 

12.观察取值函数的用户,从中找到person的代码

Iterator iter = person.getCourses().iterator();

           int count = 0;

           while(iter.hasNext()){

                 Course each = (Course)iter.next();

                 if(each.isAdvanced()){

                      count++;

                 }

      }

 

 

13、提炼函数,搬移到person中

class Person{

     

      int numberOfAdvancedCourses(Person person){

           Iterator iter = person.getCourses().iterator();

           int count = 0;

           while(iter.hasNext()){

                 Course each = (Course)iter.next();

                 if(each.isAdvanced()){

                      count++;

                 }

           }

           return count;

}

 

 

 

 

重新组织函数:

提炼函数,内联函数,内联临时变量,以查询取代临时变量,引入解释性变量,

移除对参数的赋值,以函数对象取代函数,替换算法

 

Remove Assignments to Parameters(移除对参数的赋值)

代码对一个参数进行赋值

以一个临时变量取代该参数的位置

 

 

int discount(int inputVal,int quantity,int yearToDate){

           if(intputVal>50){

                 intputVal -= 2;

           }

      }

     

      int discount(int inputVal,int quantity,int yearToDate){

           int result = inputVal;

           if(intputVal>50){

                 result -= 2;

           }

      }

因为它降低了代码的清晰度,而且混用了按值传递和按引用传递这两种参数传递方式,java只采用按值传递方式。

在java中不要对参数赋值

 

 

做法:

1. 建立一个临时变量,把待处理的参数值赋予它,

2. 以对参数的赋值为界,将其后所有对此参数的引用点,全部替换为对此临时变量的引用,

3. 修改赋值语句,使其改为对新建之临时变量赋值

4. 编译测试

 

1

      int discount(int inputVal,int quantity,int yearToDate){

           if(intputVal>50){

                 intputVal -= 2;

           }

           if(quantity>100){

                 inputVal -=1;

           }

           if(yearToDate>10000){

                 inputVal -= 4;

           }

           return inputVal;

      }

      2以临时变量取代对参数赋值

      int discount(int inputVal,int quantity,int yearToDate){

           int result = inputVal;

           if(intputVal>50){

                 result -= 2;

           }

           if(quantity>100){

                 result -=1;

           }

           if(yearToDate>10000){

                 result -= 4;

           }

           return result;

      }

 

  1. 还可以加上final,从而强调它遵循不对参数赋值这一惯例
  2. int discount(finalint inputVal,finalint quantity,finalint yearToDate){
  3.       int result = inputVal;
  4.       if(intputVal>50){
  5.             result -= 2;
  6.       }
  7.       if(quantity>100){
  8. 10.             result -=1;
  9. 11.       }
  10. 12.       if(yearToDate>10000){
  11. 13.             result -= 4;
  12. 14.       }
  13. 15.       return result;

16. }

 

 

 

1.

      public static void main(String[] args) {

          

           int x = 5;

           triple(x);

           System.out.println("x after triple:"+x);

      }

      private static void triple(int arg){

           arg = arg* 3;

           System.out.println("arg in triple :"+arg);

      }

arg in triple :15

x after triple:5

 

 

2.

           Date d1 = new Date("1 Apr 98");

           nextDateUpdate(d1);

           System.out.println("d1 after nextDay:"+d1);

          

           Date d2 = new Date("1 Apr 98");

           nextDateReplace(d2);

           System.out.println("d2 after nextDay:"+d2);

      }

     

      private static void nextDateUpdate(Date arg){

           arg.setDate(arg.getDate()+1);

           System.out.println("arg int nextDay :"+arg);

          

      }

      private static void nextDateReplace(Date arg){

           arg = new Date(arg.getYear(),arg.getMonth(),arg.getDate()+1);

           System.out.println("arg int nextDay"+arg);

      }

     

 

 

在对象之间搬移特性

搬移字段,搬移函数,提炼类,将类内联化,隐藏委托关系

移除中间人,引入外加函数,引入本地扩展

 

 

重新组织数据

Change Reference to Value (将引用对象改为值对象)

 

你有一个引用对象,很小且不可变,而且不容易管理

 

 

引用对象必须被某种方式控制,你总是必须向其控制者请求适当的引用对象,值对象有一个非常重要的特性,它们应该是不可变的,无论何时,只要你调用同一个对象的同一个查询函数,都应该得到相同的结果,如果保证了这一点,就可以放心地多个对象表示同一个事物

做法:

1、         检查重构目标是否为不可变对象,或者是否修改为不可变对象

2、         建立equalshashCode

3、         编译测试

4、         考虑是否可以删除工厂函数,并将构造函数声明为public

class Currency{

      private String _code;

     

      public String getCode(){

           return _code;

      }

     

      private Currency(String code){

           _code = code;

      }

     

 

}

//这个类返回一个货币种类代码

//他是一个引用对象

Currency usd = Currency.get("USD");

 

//要把一个引用对象变成值对象关键动作是检查它是否可变

//在这里Currency是不可变的

 

public boolean equals(Object arg){

      if(!(arg instanceof Currency)){

           return false;

      }

      Currency other = (Currency)arg;

      return (_code.equals(other._code));

}

//实现hashCode有个简单的办法读取equals使用的所有字段

//hash码然后对它们异位操作^

public int hashCode(){

      return _code.hashCode();

}

 

//我想创建多少个等值的Currency对象就可以创建多少个,

//我还可以把构造函数声明为public

//直接以构造函数获取Currency实例

//从而去掉 Currency类中的工厂函数和控制实例的创建行为

posted on 2016-02-24 21:24  Sharpest  阅读(609)  评论(0)    收藏  举报