用静态工厂方法代替构造器
首先,要明白构造器的作用是什么,而静态工厂代替了它什么。众所周知,构造器是和类名同名的一个特殊的类方法,特殊之处在于它不需要指定返回值类型,构造器的作用就是实例化一个对象。静态工厂方法代替的就是构造器的这个作用。
比如这里
Integer i = Integer.parseInt("1");
我们使用Integer下的一个静态方法创建了一个Integer对象,而不是采用new的方式。通过这个例子并不能看出静态工厂方法有什么优点和缺点,那么,接下来分析一个静态工厂方法的优点。
静态工厂方法的优点
静态工厂方法拥有更具体的名称
对于BigInteger这个类,拥有构造器BigInteger(int, int , Random),可以用它来产生一个数,而如果想直接让这个方法返回一个素数,可以用该方法下的BigInteger.probablePrime,这样子能够让调用者更直观我们产生了一个怎么样的对象。
举个例子
public class Person {
private String gender;
private Person(String gender) {
this.gender = gender;
}
public static Person woman() {
return new Person("male");
}
public static Person man() {
return new Person("man");
}
}
这个类中有一个私有的构造器,因此唯一能够获取该类的实例只能调用两个静态方法,而通过两个静态方法的名字,我们能够很清楚的知道返回的是一个“男人”还是一个“女人”。
返回相同的对象
实例受控的类(instance-controlled),简单讲就是能够随时提供相同的对象,实例受控可以确保它是一个单例或者说是不可实例化的。
这种方法类似于享元模式,如果程序经常创建”一样“的对象且创建的开销很大时,采用静态工厂方法就可以极大的提高性能。在Spring框架开发Web的情况下,我们无论是连接一个MySQL,Oracle,还是Redis,Zookeeper等,都需要维持一个Connection,而这个Connection就像是一条高速公路,需要传递的信息就像是路上的车,我们没有必要为每辆车的通行修一条路,换句话说修一条路的代价是十分高的。所以,在Spring中,框架自动的将Connection维护为一个全局的单例对象。
举个例子,要求每次返回的person都必须是一样的
public class Person {
private Person() {}
private static Person person = new Person();
public Person getPerson() {
return person;
}
}
这个类中,我们首先将的构造器给私有化了,表示我们不希望这个类被其他类实例化。同时我们提供了一个静态工厂方法,返回一个已经被我们实例化好的Person对象,而这个对象在这个类中维护的是同一个对象,因此,我们就保证了其单例的性质。
可以返回任何子类型或者实现
该优点是建立在面向对象的”多态“前提基础上,这项技术适用于基于接口的框架。
举个例子,我们想让让两个静态工厂方法返回一个交通工具,且这一个交通工具以不同的方式运行。这句话非常拗口,说白了就是返回的类型是一样的,但是做同样的事情有不同的效果。
public abstract class Vehicle {
protected abstract void drive();
private static class Car extends Vehicle {
@Override
protected void drive() {
System.out.println("on the road");
}
}
private static class Plane extends Vehicle {
@Override
protected void drive() {
System.out.println("fly in the sky");
}
}
public static Vehicle getACar() {
return new Car();
}
public static Vehicle getAPlane() {
return new Plane();
}
public static void main(String[] args) {
Vehicle car = Vehicle.getACar();
Vehicle plane = Vehicle.getAPlane();
car.drive();
plane.drive();
}
}
很显然,这个方法的控制台输出为
on the road
fly in the sky
是不是有点感觉,在我们使用的很多工具都是这样子,比如我们使用的slf4j日志门面框架,通过所谓的协调器,就可以将不同的日志实现框架,通过slf4j暴露出来。这种统一暴露,隐藏实现的模式非常有趣和重要,在开发时要多悟。
具体实现可以随时更换而不影响系统
举一个场景,有一个静态工厂方法返回一个交通工具,目前它返回的是一个继承自Vehicle的Plane,但是现在系统升级,我们希望飞机升级为战斗机,我们可以直接这样做
public static Vehicle getAPlane() {
//return new Plane();
return new WarCraft(); // WarCraft extends Vehicle
}
这样子,我们可以在不改动系统的其他的任何地方做到平滑的代码升级。这种应用场景有,当我们将第三方功能引入我们的项目时,为了减少对我们项目的侵入性,我们可以使用静态工厂方法返回一个本项目的类(处理过第三方功能)。往后,假设需要更换第三方功能实现,就可以仅仅修改静态工厂方法。
方法所返回的对象,在编写包含该静态工厂方法类时可以不存在
这种静态工厂方法构成了服务提供者框架
服务提供者框架:多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现,并把它们从多个实现中解耦出来
简单来说,就是服务的实现,服务本身,客户端时完全分开的。
任何一个所谓的服务,都需要保证客户端能够调用服务,服务提供者可以去注册服务这两个基本功能,因此,对于这种模式,又是如何实现这两点的呢?
服务提供者框架有三个重要角色:服务接口,提供者注册API,服务访问API,顾名思义,服务注册API就是提供者注册服务的方式,服务访问API就是使用服务接口的方式,而他们中间的桥梁就是服务接口。这里的接口不是Interface,只是一种抽象的概念
同时为了让服务方便的注册服务,还可以使用第四个组件:服务提供者接口。是个服务提供者使用的,通过该接口去调用提供者注册API,进入注册服务,如果不存在该组件,服务提供者可以使用反射的方式注册自己。
举个非常熟悉的例子,JDBC。
forName("com.jdbc.mysql.Driver");
这句代码在学习JDBC开始的时候经常见到,这就是通过反射的方式注册服务。而它注册的时什么服务呢?
DrvierManager.registryDrivrer();
这下我们就明白了,它注册了一个驱动,而这里的DriverManager就是组件之一的服务接口。但是,讲到这里是不是还是没有讲到静态工厂方法的事情?接下来就是
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
SQLException reason = null;
// 这里!!!!!!!!!!!!!!!!!!!!!
for(DriverInfo aDriver : registeredDrivers) {
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
这个静态方法中的registeredDrivers很明显不是提前预设,喜欢看源码的朋友可以发现DriverManager类加载的时候就尝试去获取可能被注册驱动。
总而言之,驱动是开发的时候根据数据库的类型导入的,而开发的时候我们却没有注册任何驱动,这就是所谓的静态工厂方法返回的类,在其编写是可以不存在。
静态工厂方法的缺点
1.静态工厂方法提倡不实例化,而这带来的问题就是不能子类化。
2.API的数量暴增,不易记住。每种返回的对象都设置一个静态方法,那么方法的数量肯定是非常多的。
git地址 https://github.com/JayHooooooo/effectiveJava ,点个star吧,这将激励我创作!