Effective-Java读书笔记1-静态工厂方法

摘要:本文

Effective-Java-2rd-Note

从前看这些设计模式还是 Java 设计方面的书,每次雄心壮志想好好看一下,但是每次都没有坚持下来,总结一下其原因是没有使用过,书中提到的那些经验感觉很遥远,无法感知其真正的意义所在。通过不到一年时间的 Java 开发,我觉得必要重新回过头再回顾一下这些方法论,尤其是理论和实践相结合的书籍,提升自己的 Java 设计水平。

首先从两本书下手一个就是 Effective Java, 另一本选择 设计模式只禅,这本书看了几章感觉还不错,有很多经验之谈。对于 Effective Java 这本经典著作,我准备详细的记录没一点,不论是经验还是所列举的具体事例。

Item 1: Consider static factory methods instead of constructors

静态工厂方法替代构造函数

优势:

1. 可以任意命名,更清晰,不依赖 javadoc。 这一点很好理解。

2. 不需要使用的时候创建新的对象,也就是 new 操作。

举例 Boolean.valueOf(boolean) 就是通过静态方法创建 Boolean 对象,不需要在外面 new 对象。

其次提出一个享元模式Flyweight) ,该模式以后在设计模式中具体学习。

最后提出了通过该方法可以有效的控制对象的实例化instance-controlled)。这里只是简单列举了原因:单例模式或非实例化,使得不可变类可以确保不会存在两个相等的实力,即当且仅当 a==b 才有 a.equlas(b)为 true

3. 可以返回类型的任意子类型对象

基于接口的框架应用静态工厂方法较多。

例子:java.util.Collections 包含 32个静态方法,比如

1
2
3
4
1690    public static <T> Set<T> More ...synchronizedSet(Set<T> s) {
1691 return new SynchronizedSet<>(s);
1692 }
1693

java.util.EnumSet, 没有共有的构造器,只有静态工厂方法。将会返回两种实现类,这两种实现类是根据包含枚举类型数据多少而决定的。这里可以参考该博文,具体说明了 EnumSet 的使用。 EnumSet 是使用 Long 的每一位来记录枚举类型,所以其中实现类RegularEnumSet最后包含64个元素,如果超过则使用需要使用JumboEnumSet

最后介绍的 服务提供者框架 (Service Provider Framework)这个框架是非常有用的,对于编写可复用的代码具有指导性意义。

包含四个组件

  1. service interface
  2. provider registration API
  3. service access API
  4. service provider interface
服务提供者框架
1. 定义

多个服务提供者实现一个服务,系统为客户端提供多个实现,并把它们从多个实现中解耦出来。

相关组件:

  • 服务接口
  • 服务提供者接口
  • 提供者注册API
  • 服务访问API
2. 实现,参考JDBC、JMS

3. 代码
  • Service.java
1
2
3
4
5
public interface Service {

public void service();

}
  • ServiceImpl.java
1
2
3
4
5
6
7
8
public class ServiceImpl implements Service {

@Override
public void service() {
System.out.println("service by " + ServiceImpl.class.getSimpleName());
}

}
  • Provider.java
1
2
3
4
5
public interface Provider {

public Service newService();

}
  • ProviderImpl.java
1
2
3
4
5
6
7
8
9
10
11
12
public class ProviderImpl implements Provider {

static {
Services.registerProvider(ProviderImpl.class.getName(), new ProviderImpl());
}

@Override
public Service newService() {
return new ServiceImpl();
}

}
  • 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Main {

public static void main(String[] args) {
try {
Class.forName("com.tencent.victor.ProviderImpl");
Service service = Services.newService("com.tencent.victor.ProviderImpl");
service.service();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

}

4: 在创建参数化类型实例的时候,代码更简洁

文中说了需要提供两次类型,但是从 Java 7 后,出现了类型推导(Type inference),所以不足被 Java 弥补了。所以静态工厂方法的这点优势就不重要了。不过我们还是理解一下如果 Java 没有提供类型推导,静态工厂方法是如何做到的呢?

实际上就是利用了泛型的模板机制,通过定义静态工厂方法,直接实例对应模板的对象,这样在调用静态工厂方法的时候,只需要提供泛型的模板,就可以返回对应的对象实例。

比如这个是没有类型推导,使用构造函数实例

1
Map<String, List<String>> m = new HashMap<String, List<String>>();

下面这个是定义了一个静态工厂方法来实例对应的对象

1
2
3
4
5
public static <K, V> HashMap<K, V> newInstance() { 
return new HashMap<K, V>();
}

Map<String, List<String>> m = HashMap.newInstance();

这里关键在于 static 后面的 <K, V>,也就先定义两个泛型模板,然后实例化队形的类型和返回值都是用这对模板,这样我们在使用的时候,根据我们需要返回的类型, newInstance 就可以推导出实例化时候的 KV 究竟是什么。这里在实例化的时候,K 就是 String ,而 V 就是 List<String>

Java 7以后在实例化的时候,后面的类型信息不需要了,也就是说 new 这个操作肯定也检查了返回值的类型信息,然后自动在实例化的时候进行了推导,实现原理应该大同小异。

缺点

类如果不包含公用的或者受保护啊的构造函数,不能被子类化

对于公有的静态工厂所返回的非公有类,也同样如此。如,你想讲Collections Framework中的任何方便的实现类子类化,是不可能的。
但是这样有时候也有好处,即它鼓励程序员使用复合(compostion),而不是继承。

什么意思呢,

他们与其他的静态方法实际上没有任何区别

在API文档中,他们没有像构造器那样在API文档中明确标识出来,因此对于提供了静态工厂方法而不是构造器的类来说,要想查明如何实例化一个类,这是非常困难的。javadoc工具总有一天会注意到静态工厂方法。同时,你通过在类或者接口注释中关注静态工厂,并遵守标准的命名习惯,也可以弥补这一劣势。

(目前 javadoc 好像还没专门考虑静态工厂方法)

下面是静态工厂的一些惯用名称:

  1. valueOf,不太严格讲,该方法返回的实例与它的参数具有相同的值。这样的静态工厂方法实际上是类型转换方法。
  2. of,valueOf的一种更为简洁的替代,在 EnumSet 中使用并流行起来。
  3. getInstance,返回的实例是通过方法的参数来描述的。但是不能够说与参数具有同样的值。对于单例Singleton来说,该方法没有参数,并返回唯一的实例。
  4. newInstance,向 getInstance 一样,但 newInstance 能够确保返回的每个实例都与所有其他实例不同。
  5. getType,就像 getInstance 一样,但是在工厂方法处于不同的类的时候使用(子类)。Type 表示工厂方法所返回的对象类型。
  6. newType,就像 newInstance 一样,但是在工厂方法处于不同的类的时候使用(子类)。Type 表示工厂方法所返回的对象类型。

这种命名规则可以参考。