周末闲来写写看书总结,今天写《重构》中的3个重要手法,分别是Replace Type Code With Class、Replace Type Code With Subclass和Replace Type Code With State/Strategy。
1、Replace Type Code With Class
重构前的主体代码如下:
1 package nelson; 2 3 public class Person { 4 5 public static final int O = 0; 6 public static final int A = 1; 7 public static final int B = 2; 8 public static final int AB = 3; 9 10 private int _bloodGroup; 11 12 public Person(int bloodGroup){ 13 _bloodGroup = bloodGroup; 14 } 15 16 public void setBloodGroup(int arg){ 17 _bloodGroup = arg; 18 } 19 20 public int getBloodGroup(){ 21 return _bloodGroup; 22 } 23 }
重构前的应用代码如下:
1 package nelson; 2 3 public class HelloJava { 4 5 public static void main(String[] args) { 6 7 System.out.println("Hello,Java"); 8 9 Person xiaoming = new Person(Person.A); //小明A型血 10 11 System.out.println("小明的血型是:"+xiaoming.getBloodGroup()); 12 } 13 14 }
重构前的代码有什么问题呢,在Person中使用public static final修饰了几个变量,这几个变量是血型的类型值。在new对象或者setBloodGroup时可以将此作为参数传入。问题有如下:在New对象或者setBloodGroup时,可以传入其他参数,另外Person.A这样的形式不能直观反映A的含义,如果取为BloodGroup.A--血型,血型中的A型血,是不是更直接明了。
重构后的主体代码:
1 package nelson; 2 3 public class Person { 4 5 private BloodGroup _bloodGroup; 6 7 public Person(BloodGroup bloodGroup){ 8 _bloodGroup = bloodGroup; 9 } 10 11 public void setBloodGroup(BloodGroup arg){ 12 _bloodGroup = arg; 13 } 14 15 public BloodGroup getBloodGroup(){ 16 return _bloodGroup; 17 } 18 } 19 20 class BloodGroup 21 { 22 public static final BloodGroup O = new BloodGroup(0); 23 public static final BloodGroup A = new BloodGroup(1); 24 public static final BloodGroup B = new BloodGroup(2); 25 public static final BloodGroup AB = new BloodGroup(3); 26 public static final BloodGroup[] _values = {O,A,B,AB}; 27 28 private final int _code; 29 30 private BloodGroup(int code) 31 { 32 _code = code; 33 } 34 35 public int getCode() 36 { 37 return _code; 38 } 39 40 public BloodGroup getBloodGroup(int arg) 41 { 42 return _values[arg]; 43 } 44 }
重构后的应用代码:
1 package nelson; 2 3 public class HelloJava { 4 5 public static void main(String[] args) { 6 7 System.out.println("Hello,Java"); 8 9 Person xiaoming = new Person(BloodGroup.A); //小明A型血 10 11 System.out.println("小明的血型是:"+xiaoming.getBloodGroup().getCode()); 12 } 13 }
重构后比重构前有哪些优势呢?
new Person时需要传入BloodGroup类型参数而不再是int类型参数,这样就有参数类型检查了。参数为BloodGroup.A这样就更容易理解了。
其实以上就是用class来完成枚举enum的实现了。
2、Replace Type Code With Subclass
重构前的主体类Employee,代表员工,有3中类型(Type)。
1 package nelson; 2 3 public class Employee { 4 5 private int _type; //员工类型 6 public static final int ENGINEER = 0; 7 public static final int SALEMAN = 1; 8 public static final int MANAGER = 2; 9 10 public Employee(int type) 11 { 12 _type = type; 13 } 14 15 public int getType() 16 { 17 return _type; 18 } 19 }
重构前的应用代码:
1 package nelson; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class HelloJava { 7 8 public static void main(String[] args) { 9 10 System.out.println("Hello,Java"); 11 12 Employee xiaotang = new Employee(Employee.ENGINEER); //小唐是工程师 13 Employee xiaoFang = new Employee(Employee.SALEMAN); //小方是销售 14 Employee laozhou = new Employee(Employee.MANAGER); //老周是经理 15 16 List<Employee> allStaffs = new ArrayList<Employee>(); //所有员工 17 18 allStaffs.add(xiaotang); 19 allStaffs.add(xiaoFang); 20 allStaffs.add(laozhou); 21 22 //为所有员工发年终奖,喜闻乐见 23 System.out.println(" 为所有员工发年终奖"); 24 for(Employee staff : allStaffs) 25 { 26 switch(staff.getType()) 27 { 28 case Employee.ENGINEER: System.out.println("多发一个月工资");break; 29 case Employee.SALEMAN: System.out.println("多发1.5个月工资");break; 30 case Employee.MANAGER:System.out.println("多发2个月工资");break; 31 default:break; 32 } 33 } 34 35 System.out.println(" 确定所有员工的春节放假时间"); 36 for(Employee staff : allStaffs) 37 { 38 switch(staff.getType()) 39 { 40 case Employee.ENGINEER: System.out.println("休息7天");break; 41 case Employee.SALEMAN: System.out.println("休息10天");break; 42 case Employee.MANAGER:System.out.println("休息5天");break; 43 default:break; 44 } 45 } 46 } 47 }
这里有什么问题呢,看起来逻辑也是很清晰的。问题在于,应用代码中需要不断地判断员工类型。也可以将发放年终奖做成一个函数定义在Employee中,如PaidAnnualBonus(),将春节休假时间做成一个函数定义在Employee中,如SpringFestivalVacationTime(),可以确定是这两个函数里依然会对员工类型做判断。
重构后的主体类:
1 package nelson; 2 3 public abstract class Employee { 4 5 public static final int ENGINEER = 0; 6 public static final int SALEMAN = 1; 7 public static final int MANAGER = 2; 8 9 public Employee() 10 { 11 } 12 13 public static Employee Create(int type) 14 { 15 switch(type) 16 { 17 case ENGINEER: return new Engineer(); 18 case SALEMAN: return new Salesman(); 19 case MANAGER: return new Manager(); 20 default:throw new IllegalArgumentException("Incorrect type code value"); 21 } 22 } 23 24 abstract int getType(); 25 abstract void PaidAnnualBonus(); //发年终奖 26 abstract void SpringFestivalVacationTime(); //春节放假时间 27 } 28 29 class Engineer extends Employee 30 { 31 32 public Engineer() { 33 super(); 34 } 35 36 public int getType() 37 { 38 return Employee.ENGINEER; 39 } 40 41 public void PaidAnnualBonus() 42 { 43 System.out.println("我是工程师,我年终奖多发一个月工资"); 44 } 45 46 public void SpringFestivalVacationTime() 47 { 48 System.out.println("我是工程师,我春节放假7天"); 49 } 50 } 51 52 class Salesman extends Employee 53 { 54 55 public Salesman() { 56 super(); 57 } 58 59 public int getType() 60 { 61 return Employee.SALEMAN; 62 } 63 64 public void PaidAnnualBonus() 65 { 66 System.out.println("我是销售员,我年终奖多发一个半月工资"); 67 } 68 69 public void SpringFestivalVacationTime() 70 { 71 System.out.println("我是销售员,我春节放假10天"); 72 } 73 } 74 75 class Manager extends Employee 76 { 77 78 public Manager() { 79 super(); 80 } 81 82 public int getType() 83 { 84 return Employee.MANAGER; 85 } 86 87 public void PaidAnnualBonus() 88 { 89 System.out.println("我是经理,我年终奖多发两个月工资"); 90 } 91 92 public void SpringFestivalVacationTime() 93 { 94 System.out.println("我是经理,我春节放假5天"); 95 } 96 }
重构后的应用代码:
1 package nelson; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class HelloJava { 7 8 public static void main(String[] args) { 9 10 System.out.println("Hello,Java"); 11 12 Employee xiaotang = Employee.Create(Employee.ENGINEER); //小唐是工程师 13 Employee xiaoFang = Employee.Create(Employee.SALEMAN); //小方是销售 14 Employee laozhou = Employee.Create(Employee.MANAGER); //老周是经理 15 16 List<Employee> allStaffs = new ArrayList<Employee>(); //所有员工 17 18 allStaffs.add(xiaotang); 19 allStaffs.add(xiaoFang); 20 allStaffs.add(laozhou); 21 22 //为所有员工发年终奖,喜闻乐见 23 System.out.println(" 为所有员工发年终奖"); 24 25 for(Employee staff : allStaffs) 26 { 27 staff.PaidAnnualBonus(); 28 } 29 30 System.out.println(" 确定所有员工的春节放假时间"); 31 for(Employee staff : allStaffs) 32 { 33 staff.SpringFestivalVacationTime(); 34 } 35 } 36 }
重构后的应用代码变得很简洁,利用了Employee的Create函数,应用类根本不用知道Engineer类、Salesman类、Manager类的存在。
3、Replace Type Code With State/Strategy
关于这一点的动机,参照《重构》好好理解吧。参见下图:
Replace Type Code With Subclass和Replayce Type Code With State/Strategy很类似,Replace-State/Strategy更彻底,将type单独列出来作为一个类,并将它作为宿主类的一个参数,好处就是上图中介绍的,“类型吗的值在对象生命周期中发生变化”和“其他原因使得宿主类不能被继承”。这两句话比较难懂,但考虑这样一个问题就好理解了。上面的Replace-Subclass例子中,如果一个对象被定义为了工程师,现在他得到了提升变为了经理,上面的代码就很难做到。后面“其他原因使得宿主类不能被继承”这里的其他原因确实还没想好(囧)。
重构前的代码跟上面Replace Type Code With Subclass一样,也就是Replace Type Code With State/Strategy是Repace Type Code With Subclass的升级版。
重构后的主体代码如下:
1 package nelson; 2 3 public class Employee { 4 5 EmployeeType employeeType; 6 7 public Employee() 8 { 9 } 10 11 public void setType(int arg) 12 { 13 employeeType = EmployeeType.Create(arg); 14 } 15 16 public void PaidAnnualBonus() 17 { 18 employeeType.PaidAnnualBonus(); 19 } 20 21 public void SpringFestivalVacationTime() 22 { 23 employeeType.SpringFestivalVacationTime(); 24 } 25 } 26 27 abstract class EmployeeType 28 { 29 public static final int ENGINEER = 0; 30 public static final int SALEMAN = 1; 31 public static final int MANAGER = 2; 32 33 abstract int getType(); 34 abstract void PaidAnnualBonus(); //发年终奖 35 abstract void SpringFestivalVacationTime(); //春节放假时间 36 37 public static EmployeeType Create(int type) 38 { 39 switch(type) 40 { 41 case ENGINEER: return new Engineer(); 42 case SALEMAN: return new Salesman(); 43 case MANAGER: return new Manager(); 44 default:throw new IllegalArgumentException("Incorrect type code value"); 45 } 46 } 47 } 48 49 class Engineer extends EmployeeType 50 { 51 52 public Engineer() { 53 super(); 54 } 55 56 public int getType() 57 { 58 return EmployeeType.ENGINEER; 59 } 60 61 public void PaidAnnualBonus() 62 { 63 System.out.println("我是工程师,我年终奖多发一个月工资"); 64 } 65 66 public void SpringFestivalVacationTime() 67 { 68 System.out.println("我是工程师,我春节放假7天"); 69 } 70 } 71 72 class Salesman extends EmployeeType 73 { 74 75 public Salesman() { 76 super(); 77 } 78 79 public int getType() 80 { 81 return EmployeeType.SALEMAN; 82 } 83 84 public void PaidAnnualBonus() 85 { 86 System.out.println("我是销售员,我年终奖多发一个半月工资"); 87 } 88 89 public void SpringFestivalVacationTime() 90 { 91 System.out.println("我是销售员,我春节放假10天"); 92 } 93 } 94 95 class Manager extends EmployeeType 96 { 97 public Manager() { 98 super(); 99 } 100 101 public int getType() 102 { 103 return EmployeeType.MANAGER; 104 } 105 106 public void PaidAnnualBonus() 107 { 108 System.out.println("我是经理,我年终奖多发两个月工资"); 109 } 110 111 public void SpringFestivalVacationTime() 112 { 113 System.out.println("我是经理,我春节放假5天"); 114 } 115 }
重构后的应用代码如下:
1 package nelson; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class HelloJava { 7 8 public static void main(String[] args) { 9 10 System.out.println("Hello,Java"); 11 12 Employee xiaotang = new Employee(); 13 xiaotang.setType(EmployeeType.ENGINEER); //小唐是工程师 14 Employee xiaoFang = new Employee(); 15 xiaoFang.setType(EmployeeType.SALEMAN); //小方是销售 16 Employee laozhou = new Employee(); 17 laozhou.setType(EmployeeType.MANAGER); //老周是经理 18 19 List<Employee> allStaffs = new ArrayList<Employee>(); //所有员工 20 21 allStaffs.add(xiaotang); 22 allStaffs.add(xiaoFang); 23 allStaffs.add(laozhou); 24 25 //为所有员工发年终奖,喜闻乐见 26 System.out.println(" 为所有员工发年终奖"); 27 28 for(Employee staff : allStaffs) 29 { 30 staff.PaidAnnualBonus(); 31 } 32 33 System.out.println(" 确定所有员工的春节放假时间"); 34 for(Employee staff : allStaffs) 35 { 36 staff.SpringFestivalVacationTime(); 37 } 38 } 39 }
重构后的好处就像上面的两条动机那样,Employee类现在就可以动态改变员工类型属性了,还有就是Employee类也可以被方便的继承而不受约束。