Extract Creation Class
提取创建类
撰文/Joshua Kerievsky 编译/透明
一个类中有太多的创建方法,以至于无法从代码中看清这个类主要的责任。
将一组相互关联的类中的创建方法搬到一个创建类中去。
动机
从根上来说,这个重构其实就是Extract Class[Fowler],只不过是对类的创建方法进行的。一个类中存在创建方法,这很正常;但是随着创建方法越来越多,类本身的主要责任——它存在的主要目的——可能就变得越来越模糊,甚至被对象创建的逻辑给遮蔽住了。如果情况真是这样,就应该对这个类做一些调整,将创建方法移动到一个专门的创建类(Creation Class)中去,还这个类以本来面目。
创建类和抽象工厂(Abstract Factory)[GoF]都可以用来创建一族中的任意一种对象,从这一点来说,它们很相似。但是,它们之间的差异也很大,如下表所示:
创建类
抽象工厂
运行时可替换
是
否
可以实例化一族产品
是
是
容易支持新产品
是
否
接口与实现分离
可能
是
使用静态(static)方法实现
通常是
否
作为单件(Singleton)实现
否
经常是
一般来说,如果你希望只用一个类来创建对象,那么创建类会比较合适,因为不必在运行时更换对象,直接用创建类的静态方法来创建即可。尽管创建类和抽象工厂都经常用于创建一组相关的对象,但是这两者之间有一个最大的不同之处:你不会在运行时更换创建类,因为你不需要改变产品对象的家族。创建类通常被实现为一个包含静态方法的类,每个静态方法生成并返回一个对象实例。
沟通
重复
简化
当对象的创建在一个类的公开接口中占据了一大半的时候,这个类就已经不能很好地表述自己的主要用途了。创建一个特定的类专门用于创建对象实例,这样两个类都可以更好地表达自己的用途,更好地与使用者沟通。
在这个重构中,重复不是问题。
当创建对象的责任过多地与这个类的主要责任搅在一起的时候,这个类就无法简单。将创建代码提取到一个独立的创建类中,这样可以简化这个类。
过程
1. 识别出创建方法泛滥的那个类(我们把它叫做“A”)。
2. 新定义一个类,作为你的创建类。它的作用是从一组相关的类中创建不同的对象,根据其具体作用给它命名。
3. 将A类中所有的创建方法都移到这个新的类中来,设置好所有的访问控制。
4. 修改所有获取新对象的调用者,让它们从新的创建类那里获取对象。
范例
虽然我使用的范例代码跟Martin Fowler的不同,但是由于我天生懒惰,所以我还是想反复使用前面的例子。那么,如果你不介意的话,我们还是来看这个傻傻的Loan的例子吧,上面的代码草图画的就是这个例子。请假想下面的范例代码是有相应的测试代码的——我没有在正文中把测试代码写出来,因为这个重构非常简单。
1、我们的Loan类有许多代码都是负责创建Loan对象的:
public class Loan {
private double notional;
private double outstanding;
private int rating;
private Date start;
private CapitalStrategy capitalStrategy;
private Date expiry;
private Date maturity;
// . . . more instances variables not shown
protected Loan(double notional, Date start, Date expiry,
Date maturity, int riskRating, CapitalStrategy strategy) {
this.notional = notional;
this.start = start;
this.expiry = expiry;
this.maturity = maturity;
this.rating = riskRating;
this.capitalStrategy = strategy;
}
public double calcCapital() {
return capitalStrategy.calc(this);
}
public void setOutstanding(double newOutstanding) {
outstanding = newOutstanding;
}
// ... more methods for dealing with the primary responsibilities of a Loan, not shown
public static Loan newAdvisor(double notional, Date start, Date maturity, int rating)
return new Loan(notional, start, null, maturity, rating, new TermLoanCapital());
}
public static Loan newLetterOfCredit(double notional, Date start,
Date maturity, int rating) {
return new Loan(notional, start, null, maturity, rating, new TermLoanCapital());
}
public static Loan newRCTL(double notional, Date start,
Date expiry, Date maturity, int rating) {
return new Loan(notional, start, expiry, maturity, rating, new RCTLCapital());
}
public static Loan newRevolver(double notional, Date start,
Date expiry, int rating) {
return new Loan(notional, start, expiry, null, rating, new RevolverCapital());
}
public static Loan newSPLC(double notional, Date start,
Date maturity, int rating) {
return new Loan(notional, start, null, maturity, rating, new TermLoanCapital());
}
public static Loan newTermLoan(double notional, Date start,
Date maturity, int rating) {
return new Loan(notional, start, null, maturity, rating, new TermLoanCapital());
}
public static Loan newVariableLoan(double notional, Date start,
Date expiry, Date maturity, int rating) {
return new Loan(notional, start, expiry, maturity, rating, new RCTLCapital());
}
}
2、创建一个名叫LoanCreator的类。之所以起这样的名字,是因为它唯一的作用就是给客户提供一个可以获取Loan实例的地方:
public class LoanCreator {
}
3. Now I move all of the Creation Methods from Loan to LoanCreator, placing LoanCreator in the same package as Loan (and it’s Capital stategies) so it has the protection level it needs to instantiate Loans:
3、现在,将Loan类中所有的创建方法都移动到LoanCreator中,把LoanCreator和Loan类放在同一个包中,这样它就可以有足够的访问权限来访问Loan类的构造子、实例化Loan对象了。
public class LoanCreator {
public static Loan newAdvisor(double notional, Date start,
Date maturity, int rating)
return new Loan(notional, start, null, maturity, rating, new TermLoanCapital());
}
public static Loan newLetterOfCredit(double notional, Date start,
Date maturity, int rating) {
return new Loan(notional, start, null, maturity, rating, new TermLoanCapital());
}
public static Loan newRCTL(double notional, Date start,
Date expiry, Date maturity, int rating) {
return new Loan(notional, start, expiry, maturity, rating, new RCTLCapital());
}
public static Loan newRevolver(double notional, Date start,
Date expiry, int rating) {
return new Loan(notional, start, expiry, null, rating, new RevolverCapital());
}
public static Loan newSPLC(double notional, Date start,
Date maturity, int rating) {
return new Loan(notional, start, null, maturity, rating, new TermLoanCapital());
}
public static Loan newTermLoan(double notional, Date start,
Date maturity, int rating) {
return new Loan(notional, start, null, maturity, rating, new TermLoanCapital());
}
public static Loan newVariableLoan(double notional, Date start,
Date expiry, Date maturity, int rating) {
return new Loan(notional, start, expiry, maturity, rating, new RCTLCapital());
}
}
4、最后,简单地修改一下调用的格式,重构就完成了。把
Loan termLoan = Loan.newTermLoan(…)
改成
Loan termLoan = LoanCreator.newTermLoan(…)
参考资料
[Fowler] Martin Fowler, etc., Refactoring, Addison-Wesley, 1999.
[GoF] Eric Gamma, etc., Design Patterns, Addison-Wesley, 1995.