周末闲来写写看书总结,今天写《重构》中的3个重要手法,分别是Replace Type Code With Class、Replace Type Code With Subclass和Replace Type Code With State/Strategy。
1、Replace Type Code With Class
重构前的主体代码如下:
package nelson; public 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;
}
}
重构前的应用代码如下:
package nelson; public class HelloJava { public static void main(String[] args) { System.out.println("Hello,Java"); Person xiaoming = new Person(Person.A); //小明A型血 System.out.println("小明的血型是:"+xiaoming.getBloodGroup());
} }
重构前的代码有什么问题呢,在Person中使用public static final修饰了几个变量,这几个变量是血型的类型值。在new对象或者setBloodGroup时可以将此作为参数传入。问题有如下:在New对象或者setBloodGroup时,可以传入其他参数,另外Person.A这样的形式不能直观反映A的含义,如果取为BloodGroup.A--血型,血型中的A型血,是不是更直接明了。
重构后的主体代码:
package nelson; public class Person { private BloodGroup _bloodGroup; public Person(BloodGroup bloodGroup){
_bloodGroup = bloodGroup;
} public void setBloodGroup(BloodGroup arg){
_bloodGroup = arg;
} public BloodGroup getBloodGroup(){
return _bloodGroup;
}
} 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);
public static final BloodGroup[] _values = {O,A,B,AB}; private final int _code; private BloodGroup(int code)
{
_code = code;
} public int getCode()
{
return _code;
} public BloodGroup getBloodGroup(int arg)
{
return _values[arg];
}
}
重构后的应用代码:
package nelson; public class HelloJava { public static void main(String[] args) { System.out.println("Hello,Java"); Person xiaoming = new Person(BloodGroup.A); //小明A型血 System.out.println("小明的血型是:"+xiaoming.getBloodGroup().getCode());
}
}
重构后比重构前有哪些优势呢?
new Person时需要传入BloodGroup类型参数而不再是int类型参数,这样就有参数类型检查了。参数为BloodGroup.A这样就更容易理解了。
其实以上就是用class来完成枚举enum的实现了。
2、Replace Type Code With Subclass
重构前的主体类Employee,代表员工,有3中类型(Type)。
package nelson; public class Employee { private int _type; //员工类型
public static final int ENGINEER = 0;
public static final int SALEMAN = 1;
public static final int MANAGER = 2; public Employee(int type)
{
_type = type;
} public int getType()
{
return _type;
}
}
重构前的应用代码:
package nelson; import java.util.ArrayList;
import java.util.List; public class HelloJava { public static void main(String[] args) { System.out.println("Hello,Java"); Employee xiaotang = new Employee(Employee.ENGINEER); //小唐是工程师
Employee xiaoFang = new Employee(Employee.SALEMAN); //小方是销售
Employee laozhou = new Employee(Employee.MANAGER); //老周是经理 List<Employee> allStaffs = new ArrayList<Employee>(); //所有员工 allStaffs.add(xiaotang);
allStaffs.add(xiaoFang);
allStaffs.add(laozhou); //为所有员工发年终奖,喜闻乐见
System.out.println("\n为所有员工发年终奖");
for(Employee staff : allStaffs)
{
switch(staff.getType())
{
case Employee.ENGINEER: System.out.println("多发一个月工资");break;
case Employee.SALEMAN: System.out.println("多发1.5个月工资");break;
case Employee.MANAGER:System.out.println("多发2个月工资");break;
default:break;
}
} System.out.println("\n确定所有员工的春节放假时间");
for(Employee staff : allStaffs)
{
switch(staff.getType())
{
case Employee.ENGINEER: System.out.println("休息7天");break;
case Employee.SALEMAN: System.out.println("休息10天");break;
case Employee.MANAGER:System.out.println("休息5天");break;
default:break;
}
}
}
}
这里有什么问题呢,看起来逻辑也是很清晰的。问题在于,应用代码中需要不断地判断员工类型。也可以将发放年终奖做成一个函数定义在Employee中,如PaidAnnualBonus(),将春节休假时间做成一个函数定义在Employee中,如SpringFestivalVacationTime(),可以确定是这两个函数里依然会对员工类型做判断。
重构后的主体类:
package nelson; public abstract class Employee { public static final int ENGINEER = 0;
public static final int SALEMAN = 1;
public static final int MANAGER = 2; public Employee()
{
} public static Employee Create(int type)
{
switch(type)
{
case ENGINEER: return new Engineer();
case SALEMAN: return new Salesman();
case MANAGER: return new Manager();
default:throw new IllegalArgumentException("Incorrect type code value");
}
} abstract int getType();
abstract void PaidAnnualBonus(); //发年终奖
abstract void SpringFestivalVacationTime(); //春节放假时间
} class Engineer extends Employee
{ public Engineer() {
super();
} public int getType()
{
return Employee.ENGINEER;
} public void PaidAnnualBonus()
{
System.out.println("我是工程师,我年终奖多发一个月工资");
} public void SpringFestivalVacationTime()
{
System.out.println("我是工程师,我春节放假7天");
}
} class Salesman extends Employee
{ public Salesman() {
super();
} public int getType()
{
return Employee.SALEMAN;
} public void PaidAnnualBonus()
{
System.out.println("我是销售员,我年终奖多发一个半月工资");
} public void SpringFestivalVacationTime()
{
System.out.println("我是销售员,我春节放假10天");
}
} class Manager extends Employee
{ public Manager() {
super();
} public int getType()
{
return Employee.MANAGER;
} public void PaidAnnualBonus()
{
System.out.println("我是经理,我年终奖多发两个月工资");
} public void SpringFestivalVacationTime()
{
System.out.println("我是经理,我春节放假5天");
}
}
重构后的应用代码:
package nelson; import java.util.ArrayList;
import java.util.List; public class HelloJava { public static void main(String[] args) { System.out.println("Hello,Java"); Employee xiaotang = Employee.Create(Employee.ENGINEER); //小唐是工程师
Employee xiaoFang = Employee.Create(Employee.SALEMAN); //小方是销售
Employee laozhou = Employee.Create(Employee.MANAGER); //老周是经理 List<Employee> allStaffs = new ArrayList<Employee>(); //所有员工 allStaffs.add(xiaotang);
allStaffs.add(xiaoFang);
allStaffs.add(laozhou); //为所有员工发年终奖,喜闻乐见
System.out.println("\n为所有员工发年终奖"); for(Employee staff : allStaffs)
{
staff.PaidAnnualBonus();
} System.out.println("\n确定所有员工的春节放假时间");
for(Employee staff : allStaffs)
{
staff.SpringFestivalVacationTime();
}
}
}
重构后的应用代码变得很简洁,利用了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的升级版。
重构后的主体代码如下:
package nelson; public class Employee { EmployeeType employeeType; public Employee()
{
} public void setType(int arg)
{
employeeType = EmployeeType.Create(arg);
} public void PaidAnnualBonus()
{
employeeType.PaidAnnualBonus();
} public void SpringFestivalVacationTime()
{
employeeType.SpringFestivalVacationTime();
}
} abstract class EmployeeType
{
public static final int ENGINEER = 0;
public static final int SALEMAN = 1;
public static final int MANAGER = 2; abstract int getType();
abstract void PaidAnnualBonus(); //发年终奖
abstract void SpringFestivalVacationTime(); //春节放假时间 public static EmployeeType Create(int type)
{
switch(type)
{
case ENGINEER: return new Engineer();
case SALEMAN: return new Salesman();
case MANAGER: return new Manager();
default:throw new IllegalArgumentException("Incorrect type code value");
}
}
} class Engineer extends EmployeeType
{ public Engineer() {
super();
} public int getType()
{
return EmployeeType.ENGINEER;
} public void PaidAnnualBonus()
{
System.out.println("我是工程师,我年终奖多发一个月工资");
} public void SpringFestivalVacationTime()
{
System.out.println("我是工程师,我春节放假7天");
}
} class Salesman extends EmployeeType
{ public Salesman() {
super();
} public int getType()
{
return EmployeeType.SALEMAN;
} public void PaidAnnualBonus()
{
System.out.println("我是销售员,我年终奖多发一个半月工资");
} public void SpringFestivalVacationTime()
{
System.out.println("我是销售员,我春节放假10天");
}
} class Manager extends EmployeeType
{
public Manager() {
super();
} public int getType()
{
return EmployeeType.MANAGER;
} public void PaidAnnualBonus()
{
System.out.println("我是经理,我年终奖多发两个月工资");
} public void SpringFestivalVacationTime()
{
System.out.println("我是经理,我春节放假5天");
}
}
重构后的应用代码如下:
package nelson; import java.util.ArrayList;
import java.util.List; public class HelloJava { public static void main(String[] args) { System.out.println("Hello,Java"); Employee xiaotang = new Employee();
xiaotang.setType(EmployeeType.ENGINEER); //小唐是工程师
Employee xiaoFang = new Employee();
xiaoFang.setType(EmployeeType.SALEMAN); //小方是销售
Employee laozhou = new Employee();
laozhou.setType(EmployeeType.MANAGER); //老周是经理 List<Employee> allStaffs = new ArrayList<Employee>(); //所有员工 allStaffs.add(xiaotang);
allStaffs.add(xiaoFang);
allStaffs.add(laozhou); //为所有员工发年终奖,喜闻乐见
System.out.println("\n为所有员工发年终奖"); for(Employee staff : allStaffs)
{
staff.PaidAnnualBonus();
} System.out.println("\n确定所有员工的春节放假时间");
for(Employee staff : allStaffs)
{
staff.SpringFestivalVacationTime();
}
}
}
重构后的好处就像上面的两条动机那样,Employee类现在就可以动态改变员工类型属性了,还有就是Employee类也可以被方便的继承而不受约束。