摘要:本文
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 | 1690 public static <T> Set<T> More ...synchronizedSet(Set<T> s) { |
java.util.EnumSet, 没有共有的构造器,只有静态工厂方法。将会返回两种实现类,这两种实现类是根据包含枚举类型数据多少而决定的。这里可以参考该博文,具体说明了 EnumSet 的使用。 EnumSet 是使用 Long 的每一位来记录枚举类型,所以其中实现类RegularEnumSet
最后包含64个元素,如果超过则使用需要使用JumboEnumSet
最后介绍的 服务提供者框架 (Service Provider Framework)这个框架是非常有用的,对于编写可复用的代码具有指导性意义。
包含四个组件
- service interface
- provider registration API
- service access API
- service provider interface
服务提供者框架
1. 定义
多个服务提供者实现一个服务,系统为客户端提供多个实现,并把它们从多个实现中解耦出来。
相关组件:
- 服务接口
- 服务提供者接口
- 提供者注册API
- 服务访问API
2. 实现,参考JDBC、JMS
3. 代码
- Service.java
1 | public interface Service { |
- ServiceImpl.java
1 | public class ServiceImpl implements Service { |
- Provider.java
1 | public interface Provider { |
- ProviderImpl.java
1 | public class ProviderImpl implements Provider { |
- 测试
1 | public class Main { |
4: 在创建参数化类型实例的时候,代码更简洁
文中说了需要提供两次类型,但是从 Java 7 后,出现了类型推导(Type inference),所以不足被 Java 弥补了。所以静态工厂方法的这点优势就不重要了。不过我们还是理解一下如果 Java 没有提供类型推导,静态工厂方法是如何做到的呢?
实际上就是利用了泛型的模板机制,通过定义静态工厂方法,直接实例对应模板的对象,这样在调用静态工厂方法的时候,只需要提供泛型的模板,就可以返回对应的对象实例。
比如这个是没有类型推导,使用构造函数实例
1 | Map<String, List<String>> m = new HashMap<String, List<String>>(); |
下面这个是定义了一个静态工厂方法来实例对应的对象
1 | public static <K, V> HashMap<K, V> newInstance() { |
这里关键在于 static
后面的 <K, V>
,也就先定义两个泛型模板,然后实例化队形的类型和返回值都是用这对模板,这样我们在使用的时候,根据我们需要返回的类型, newInstance
就可以推导出实例化时候的 K
和 V
究竟是什么。这里在实例化的时候,K
就是 String
,而 V
就是 List<String>
。
Java 7
以后在实例化的时候,后面的类型信息不需要了,也就是说 new
这个操作肯定也检查了返回值的类型信息,然后自动在实例化的时候进行了推导,实现原理应该大同小异。
缺点
类如果不包含公用的或者受保护啊的构造函数,不能被子类化
对于公有的静态工厂所返回的非公有类,也同样如此。如,你想讲Collections Framework中的任何方便的实现类子类化,是不可能的。
但是这样有时候也有好处,即它鼓励程序员使用复合(compostion),而不是继承。
什么意思呢,
他们与其他的静态方法实际上没有任何区别
在API文档中,他们没有像构造器那样在API文档中明确标识出来,因此对于提供了静态工厂方法而不是构造器的类来说,要想查明如何实例化一个类,这是非常困难的。javadoc工具总有一天会注意到静态工厂方法。同时,你通过在类或者接口注释中关注静态工厂,并遵守标准的命名习惯,也可以弥补这一劣势。
(目前 javadoc 好像还没专门考虑静态工厂方法)
下面是静态工厂的一些惯用名称:
valueOf
,不太严格讲,该方法返回的实例与它的参数具有相同的值。这样的静态工厂方法实际上是类型转换方法。of,valueOf
的一种更为简洁的替代,在EnumSet
中使用并流行起来。getInstance
,返回的实例是通过方法的参数来描述的。但是不能够说与参数具有同样的值。对于单例Singleton来说,该方法没有参数,并返回唯一的实例。newInstance
,向getInstance
一样,但 newInstance 能够确保返回的每个实例都与所有其他实例不同。getType
,就像getInstance
一样,但是在工厂方法处于不同的类的时候使用(子类)。Type
表示工厂方法所返回的对象类型。newType
,就像newInstance
一样,但是在工厂方法处于不同的类的时候使用(子类)。Type
表示工厂方法所返回的对象类型。
这种命名规则可以参考。