当EffectiveJava遇见Guava - 静态工厂方法代替构造器(规则1)

当EffectiveJava遇见Guava - 静态工厂方法代替构造器(规则1)

Effective Java中指出,使用静态工厂方法代替构造器有几大优势:

第一大优势 - 他们有名称。

多个构造器只能通过匹配参数类型的顺序不同来区分使用哪一个,这样常常会导致用户调用错误构造器,而静态工程方法则不同,可以通过方法名清晰的指明用意。

//本例只用来说明第一大优势,请不要纠结其它问题 
public class Foo {
Set<Bar> bars;
List<Car> cars;
//构造器1
private Foo(Set<Bar> bars) {
this.bars = bars;
}
//构造器2
private Foo(List<Car> cars) {
this.cars = cars;
}
//构造器3
private Foo(Set<Bar> bars, List<Car> cars) {
this.bars = bars;
this.cars = cars;
}
//静态工厂方法1
public static Foo newInstanceByBar(){
return new Foo(new HashSet<Bar>());
}
//静态工厂方法2
public static Foo newInstanceByCar(){
return new Foo(new ArrayList<Car>());
}
//静态工厂方法3
public static Foo newInstanceByAll(){
return new Foo(new HashSet<Bar>(),new ArrayList<Car>());
}
public static void main(String[] args) {
// 通过构造器创建实例,不好区分容易使用错误
Foo fbar = new Foo(new HashSet<Bar>());
Foo fcar = new Foo(new ArrayList<Car>());
Foo fall = new Foo(new HashSet<Bar>(),new ArrayList<Car>());
// 通过静态工厂方法可以清晰的用方法名识别
Foo fbar_static = Foo.newInstanceByBar();
Foo fcar_static = Foo.newInstanceByCar();
Foo fall_static = Foo.newInstanceByAll();
}
}
class Bar {}
class Car {}

对于Guava,并没有提供创建静态工厂方法的工具,但整个Guava API到处都是静态方法的实现,我们以Guava Collections Framewrok举例说明。

Guava对于第一大优势有很多实现:

List<Type> exactly100 = Lists.newArrayListWithCapacity(100);
List<Type> approx100 = Lists.newArrayListWithExpectedSize(100);
Set<Type> approx100Set = Sets.newHashSetWithExpectedSize(100);

第二大优势 - 不必在每次调用他们的时候都创建一个新对象。

方便对象重用,还可以确保不可变的不会存在两个相等的实例,如果a==b那么a.equals.(b)才会返回true ,如果能保证这一点,就可以使用==操作符来比较对象,会有很大的性能提升。

第三大优势 - 他们可以返回原返回类型的任何子类型的对象。

这是一个非常强大的特性, Effective Java中列举了API、SPI、服务提供框架的关系来说明:

API(Service Interface): 服务公共接口 SPI(Service Provider Interface): 服务提供商接口 SPF(Service Provider Framework): 服务提供框架

看例子:

// 服务提供框架示意模型 - 服务API 
public interface ServiceAPI {
// 这里是服务指定的方法
}
// 服务提供框架示意模型 - 服务SPI
public interface ServiceSPI {
ServiceAPI newService();
}
// 服务提供框架示意模型实现
// 不可实例化的类,用来注册创建和提供访问
public class ServiceFramework {
private ServiceFramework() {
}// 强制防止实例化(规则4)

// 映射服务名到服务
private static final ConcurrentMap<String, ServiceSPI> spis = new MapMaker().makeMap();//使用Guava创建
public static final String DEFAULT_SPI_NAME = "<def>";

// 默认SPI注册API
public static void registerDefaultSPI(ServiceSPI spi) {
registerSPI(DEFAULT_SPI_NAME, spi);
}

// 指定SPI注册API
public static void registerSPI(String name, ServiceSPI spi) {
spis.put(name, spi);
}

// 服务访问API
public static ServiceAPI newInstance() {
return newInstance(DEFAULT_SPI_NAME);
}
public static ServiceAPI newInstance(String name) {
ServiceSPI spi = spis.get(name);
if(spi == null)
throw new IllegalArgumentException(
"No provider registered with name: " + name);
return spi.newService();
}
}
Note
静态工程方法返回的对象所属的类,在编写这个包含静态工厂方法的类时可以不必存在。上面的例子在编写ServiceFramework类时,ServiceAPI的实现类并不存在。这大大增加了框架的灵活性。

现在编写客户端测试程序

// 简单的服务提供框架测试程序 
public class Test {
public static void main(String[] args) {
// 服务提供商执行下面的注册
ServiceFramework.registerDefaultSPI(DEFAULT_PROVIDER);
ServiceFramework.registerSPI("comp", COMP_PROVIDER);
ServiceFramework.registerSPI("armed", ARMED_PROVIDER);
// 客户端执行下面的创建
ServiceAPI s1 = ServiceFramework.newInstance();
ServiceAPI s2 = ServiceFramework.newInstance("comp");
ServiceAPI s3 = ServiceFramework.newInstance("armed");
System.out.printf("%s, %s, %s%n", s1, s2, s3);
}
private static ServiceSPI DEFAULT_PROVIDER = new ServiceSPI() {
public ServiceAPI newService() {
return new ServiceAPI() {
@Override
public String toString() {
return "默认服务";
}
};
}
};
private static ServiceSPI COMP_PROVIDER = new ServiceSPI() {
public ServiceAPI newService() {
return new ServiceAPI() {
@Override
public String toString() {
return "Complementary 服务";
}
};
}
};
private static ServiceSPI ARMED_PROVIDER = new ServiceSPI() {
public ServiceAPI newService() {
return new ServiceAPI() {
@Override
public String toString() {
return "Armed 服务";
}
};
}
};
}

//输出如下 , Complementary , Armed

第四大优势 - 在创建参数化类型实例的时候,他们使代码变得更加简洁。

在JDK7之前,我们创建一个Collections大致是这么做的:

List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<TypeThatsTooLongForItsOwnGood>();

JDK7发布以后,我们可以简化成这样:

List<TypeThatsTooLongForItsOwnGood> list = new ArrayList<>();

但是Guava还是宁愿使用静态工程方法,因为真的非常方便:

Set<Type> copySet = Sets.newHashSet(elements); 
List<String> theseElements = Lists.newArrayList("alpha", "beta", "gamma");

静态工程方法的缺点

  • 类如果不含公有的或者受保护的构造器,就不能被子类化,这也许会因祸得福,因为它鼓励开发人员使用复合,而不是继承。

  • 他们与其他的静态方法实际上没有任何区别 如果API文档没有明确的说明这是一个静态工程方法,就会很难识别出来。遵循标准的命名规范习惯,可以弥补这一劣势,下面列出一些惯用命名:

    • valueOf - 这样的静态工厂方法实际上是类型转换

    • of - valueOf的简洁方式

    • getInstance - 返回实例通过方法参数描述,对于单例,该方法没有参数,并返回唯一的实例

    • newInstance - 与getInstance不同的是,它返回的实例与所有其它实例都是不同的

    • getType - 像getInstance一样,但是在工厂方法处于不同的类中的时候使用。Type表示i返回对象类型

    • newType - 像newInstance一样,但是在工厂方法处于不同的类中的时候使用。Type表示i返回对象类型

2013-05-29

你可能感兴趣的:(当EffectiveJava遇见Guava - 静态工厂方法代替构造器(规则1))