前言
自己java编程已有两年,自己也写过一些轮子,也在工作中针对自己以前写的代码重构过,但是距离那些优秀的类库总有一些差距,最近在看 Effective Java 第三版,书中总结甚为精辟,遂在阅读过程中逐条写下笔记,以指导自己更加有效的使用 java 编程语言及基本类库,涵盖部分jdk 7,8,9 的新特性
目的
Consider a builder when faced with many constructor parameters
遇到多个构造器参数时要考虑使用构建器
正文
例:用一个类表示包装食品外面显示的营养成分标签,其中有几个域是必须的
当有一个类需要多个参数的构造器,我们一般最开始考虑到的是重叠构造器
1 | // Telescoping constructor pattern - does not scale well! (Pages 10-11)不能很好的扩展 |
如上面 main 方法中的调用方法所示:假设我们需要设置 sodium 和 carbohydrate 的值,但是我们不想要设置 fat 的值,如上所见,所调用的构造器需要我们设置我们不想设置的参数,除非我们在编写一个构造器,内部用 set 方法初始化.
重叠构造器在有很多参数的时候,客户端代码会很难编写并且难以阅读,另外其本身也不能很好的扩展
那我们现在考虑更为普遍的一种方式: JavaBeans
1 | // JavaBeans Pattern - allows inconsistency, mandates mutability (pages 11-12)允许不一致,强制要求可变性 |
JavaBeans 方法的缺点:
1 | 由于 JavaBeans 方式将构造过程分隔到了几个调用过程中,在构造过程中 JavaBeans 可能处于不一致的状态,无法仅仅通过检查构造器参数的有效性在保证一致性 |
那么这里我们扩展一下 SpringMVC 中的单例模式,我们知道 controller -> service -> dao 这个流程,他们的对象都是单例的,想想一下这些单例的对象在处理我们传给后台的实体 bean 时会不会有问题了?如果一个张三带着正确密码在登陆的同时,一个李四在登陆,如果是单例并且状态可变,那么最后校验是张三和李四的密码导致他登陆失败?
答:是不会有问题的,因为我们的实体bean是前台的json串反序列化,或者我们自己 new,然后拼装起来的,所以他并不是单例模式。另外这也说明单例模式中存在可变域可能导致线程不安全,因此 1.在 controller 类中不要定义非单例成员变量 2.万一必须要定义一个非静态成员变量时候,则通过注解@Scope(“prototype”),将其设置为多例模式
另外上网查了下(未验证)了 JavaBeans 的反序列化的三种工具:fastJson JackJson 以及 Gson
1 | Gson是通过反射遍历该类中的所有属性,并把其值序列化成json |
第三种方法就是建造者模式
1 | public class NutritionFacts { |
从上面的链式调用可以发现,这样做确实比较优雅,但是他开销比较大。由于上面的这个建造器的属性都设置的final,所以在建造后就无法在修改了?这里有几个疑问?
1.建造器模式是否只是应用于不可变类?
2.假设我们要对建造器模式类中的一个属性值在建造后重新赋值,我们怎么做?将 final 去掉,提供 setter 方法吗?如果这样,它和静态内部类中的对应属性是否就不一致了,而且 api 混乱,导致初始化域的方法有两个?
3.建造器模式的序列化和反序列化?还是上一个问题,静态内部类的属性和建造器内的属性是否需要保持一致,如果是利用 fastjson 的话,内部和外部都得提供 getter 方法,如此种种,感觉建造器模式还是适合工具类,不太适合 web 中的 javabeans,比如表单。尽管可能提供多个参数的构造方法
4.由建造器模式的链式调用想到 JavaBeans 的 setter 方法为什么不return this了,这样就可以链式调用了?
比如 guava 中 Ordering 的链式调用
1 | /** |
1 | 类的定义 |
从上可以看到跟构造器关系不大,更符合第一点,用静态工厂方法代替构造器,基于接口编程,初始化返回其子类对象,接着在调基类接口方法