Item 1. 考虑用静态工厂方法替代构造器
获得一个类的实例时我们都会采取一个公有的构造器。Foo x = new Foo();
同时我们应该掌握另一种方法就是静态工厂方法(static factory method)。
一句话总结,静态工厂方法其实就是一个返回类的实例的静态方法。
书中给出的例子是Boolean的valueOf方法:
通过valueOf方法将boolean基本类型转换成了一个Boolean类型,返回了一个新的对象引用。
除valueOf外,像Java中的getInstance和newInstance等方法都为静态工厂方法。
静态工厂方法不同于设计模式中的工厂方法。
那么为什么要使用静态工厂方法呢?下面是它的几大优势:
它们有名字
给构造器起名字,增强了代码的可读性。
如果一个构造器的参数并不能确切描述它返回的对象,这时候可以考虑静态工厂方法。
或者你的多个构造器只是在参数列表中的参数顺序上有所不同,那么除非你提供了详尽的文档说明,否则你下次使用时就会一脸懵逼,这几个构造器到底要选哪个?????
例如下面这个例子,一个RandomIntGenerator类,从类名可以看出这是个用来产生整型随机数的类。
public class RandomIntGenerator {private final int min;
private final int max;
public int next(){...}
}
随机数的大小介于min和max两个参数之间,我们需要构造器去对它们进行初始化。
public RandomIntGenerator(int min, int max) {this.min = min;
this.max = max;
}
很好,现在我们又想提供一个新的功能,用户只需要指定一个最小值即可,生成的随机数会介于指定的最小值和整型默认的最大值之间。
所以,我们可能会添加一个新的构造器:
public RandomIntGenerator(int min) {this.min = min;
this.max = Integer.MAX_VALUE;
}
到这里事情进展很顺利,但是有指定最小值的功能,相对的我们还要添加一个指定最大值的方法。
public RandomIntGenerator(int max) {this.min = Integer.MIN_VALUE;
this.max = max;
}
但是创建完之后你会得到一个编译错误,原因是两个构造器有相同的方法签名(方法名和参数类型)。
这时静态工厂方法就派上用场了,重新构造如下:
public class RandomIntGenerator {private final int min;
private final int max;
private RandomIntGenerator(int min, int max) {
this.min = min;
this.max = max;
}
public static RandomIntGenerator between(int max, int min) {
return new RandomIntGenerator(min, max);
}
public static RandomIntGenerator biggerThan(int min) {
return new RandomIntGenerator(min, Integer.MAX_VALUE);
}
public static RandomIntGenerator smallerThan(int max) {
return new RandomIntGenerator(Integer.MIN_VALUE, max);
}
public int next() {...}
}
不仅没有了之前的错误,而且它们有着不同的名字,很清晰地描述了方法的功能。
总之,由于静态工厂方法有名称,所以他们不受那些限制。
当你有多个签名相同的构造器时,用几个名字有区分度的静态工厂方法代替可能是更好的解决办法。
不必在每次调用它们的时候创建一个新对象
每次调用构造器都会创建一个新对象,而静态工厂方法则不会。
这使得不可变类可以使用预先定义好的实例,或者将构建好的实例缓存起来,进行重复利用,避免创建不必要的重复对象。
public class BooleanGenerator {public static void main(String[] args) {
Boolean b1 = Boolean.valueOf(true);
Boolean b2 = Boolean.valueOf(true);
Boolean b3 = new Boolean(true);
Boolean b4 = new Boolean(true);
System.out.println(b1 == b2);
System.out.println(b3 == b4);
}
}
//output:
//true
//false
可以看到使用valueOf并不会创建新的对象,对于一些经常创建相同对象的程序,并且创建对象的代价很高,静态工厂方法可以极大地提升性能。
可以返回原返回类型的任何子类型的对象
在选择返回对象的类时有了更大的灵活性。
API可以返回对象,同时不会使对象的类变成公有的既可以是非公有类,这样做的目的可以隐藏实现类。
公有的静态工厂方法所返回的对象的类不仅可以是非公有的,而且该类还可以随着每次调用发生变化,这取决于静态工厂方法的参数值。
参考java.util.EnumSet中的noneOf方法,根据不同的参数类型选择返回的是RegularEnumSet还是JumboEnumSet:
接下来,书中通过服务提供者框架(Service Provider Framework)来说明了静态工厂方法的另一个用法。
利用的是静态工厂方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不必存在。
看起来有点绕,下面来通过代码来看一下。
//服务接口public interface Service(){
...//具体的服务方法
}
//服务提供者接口
public interface Provider{
Service newService();
}
//不可实例化的类,用于服务注册和访问
public class Services {
private Services{};//防止实例化
//将服务的名字映射到具体服务
private static final Map<String,Provider> providers = new ConcurrentHashMap<String, Provider>();
public static final String DEFAULT_PROVIDER_NAME = "<def>";
//服务提供者注册API
//默认的注册方法
public static void registerDefaultProvider(Provider p){
registerProvider(DEFAULT_PROVIDER_NAME,p);
}
//真正的注册方法
public void registerProvider(String name, Provider p) {
providers.put(name, p);
}
//服务访问API
public static Service newInstance() {
return newInstance(DEFAULT_PROVIDER_NAME);
}
//真正的实例化方法
public static Service newInstance(String name) {
Provider p = providers.get(name);
if(p == null) {
throw new IllegalArgumentException("No provider registered with name:" + name);
}
return p.newService();//返回服务实例
}
}
服务提供者框架是指这样一个系统:多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现,并把他们从实现中解耦出来。
这块有点难理解,先来看一下UML图:
JDBC就是利用的服务提供者框架,当我们创建数据库连接时,需要先加载对应的驱动,然后获取连接。
Class.forName(jdbcDriver);conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPasswd);
对于JDBC来说Connection就是它的服务接口,里面的方法,不同的数据库需要自己实现。
DriverManager就是Services类,其中包含的registerDriver和getConnection方法对应的就是注册和访问。
Driver是一个服务提供者接口。
最后返回的服务实际上是通过服务提供者接口,实现了解耦。
在创建参数化类型实例的时候,使得代码变得更加简洁
如果你用的是JDK1.7之前的版本在定义一个HashMap,那你不得不这么写:
Map<String, String> map = new HashMap<String, String>();
在使用构造器时必须再写一遍类型参数,因为不支持类型推到,每次都要干重复性的工作。
假设HashMap提供了静态工厂方法,事情就变得简单:
public static <K,V> HashMap<K,V> newInstance(){return new HashMap<K,V>();
}
你就可以通过下面的代码代替上面繁琐的声明:
public RandomIntGenerator(int min, int max) {this.min = min;
this.max = max;
}0
显然作者在写这本书时已经考虑到了这个问题(那时候JDK的版本是1.6),JDK1.7之后的版本有了类型推导。
当然凡事都有两面性,除了上述的优点,静态工厂方法同样存在不足。
类如果不含共有的或者受保护的构造器,就不能被子类化
如果没有公有构造器,当然这个类就不能被子类继承。
这也许是一个优点,因为鼓励程序使用组合而不是继承。
他们与其他的静态方法实际上没有任何区别
在API文档中它们没有像构造器那样被明确标识出来,因此对于一个使用静态工厂方法而不是构造器的类来说,要想弄明白如何实例化,就需要费点事了。
你可以使用注释或者如下的命名规则让用户知道这是一个静态工厂方法:
valueOf——返回的实例与它的参数具有相同的值,被用来做类型转换。e.g. String.valueOf()。
of——valueOf的一种更加简洁的替代,在EnumSet中使用并流行起来。
getInstance——通过方法的参数来描述返回实例。
newInstance——和getInstance一样,每次返回新的实例。
getType、newType——和上面两个方法类似,在工厂方法处于不同的类中时使用,Type表示工厂方法返回的对象类型。
操之,静态工厂方法和构造器各有优势,使用时需要衡量那种方法更好。
Item 2. 遇到多个构造器参数时要考虑用构建器
上一节介绍了静态工厂方法,虽然相对构造器来说有一定的优势,但是两者都有一个局限,就是存在大量厠选忂数时表现不是很好。
重叠构造器
当面圍妡量厌镰収数时,一些人可能默选用重叠构造器telescoping constructor)。
文中举了一个食品营养成分表的例子,表中有些参数是必选的,有些参数是可选的。
对于重叠构造器来说,第一个构造器只剧塌查选参数,第二个构造器有一个䍿选参損,第䏯以揖郺器攉乮个,以此类推,直到最后一个构造器包含所有参数。
public RandomIntGenerator(int min, int max) {this.min = min;
this.max = max;
}1
重叠构造器像套圈一样,对参数进行赋值。
你必须很小心地将值和参数的位置一一对应,随着参数数量的增加,你肯定不会记得第六个参数是什么。
并且如果两个类型相同参数的顺序发生了调换,可能编译期不会提示错误,但在运行时会报错。
重叠构造器模式可行,但是当有许多参数时,客户端代码会很难编写,并且可读性很差。
JavaBeans模式
另一种解决办法是JavaBeans模式,这种模式简单并且灵活,也是我们最经常使用的,通过setter方法来设置参数。
public RandomIntGenerator(int min, int max) {this.min = min;
this.max = max;
}2
JavaBeans模式弥补了重叠构造器的不足,有着良好的可实现性和可读性。
但是其也存在着不足:
JavaBeans是可变的,意思是在被创建之后它们的状态可以通过setter方法随之更改。
它们的域不能声明为final,这也使它们不能成为不可变对象,不能保证线程安全。
Builder模式
Builder模式作为一种更好的方法,既能保证安全性,还有着良好的可读性。
通过Builder类来返回一个builder对象,然后在客户端调用Builder中的方法来设置参数,最后调用builder()方法来完成创建一个不可变对象。
Builder类是一个静态的内部类,其中的方法和setter类似,并且可以实现链式调用,易于使用和阅读。
public RandomIntGenerator(int min, int max) {this.min = min;
this.max = max;
}3
View Code
还没有评论,来说两句吧...