《Effective Java》第1条:考虑静态工厂方法代替构造器

Posted by 代码无止境 on 2019-05-15

书中从优缺两个方面来阐述了静态工厂方法,也提到了静态工厂方法的应用场景,比如服务提供者框架,我也去看了一下JDBC的源码来帮助我理解服务提供者框架的概念,在这里也通过这篇文章分享给大家。

四大优势

优势一,对与构造方法而言,静态工场方法有名字

当一个类的构造方法种类繁多而且参数复杂的话,静态工厂方法的名字可以用来准确的描述返回的实例的特性,拥有比较好的可读性。

优势二,不必在每次调用的时候都返回一个新的实例

单例模式下这个优势会显得比较明显。

优势三,可以返回原返回类型的任何子类型的实例

这个优势在书中的描述篇幅最大,也是最难理解的一部分,这里我们展开来讲一下。
可以返回原返回类型的任何子类型这个特性可以让我们比较灵活的选择返回对象类型,这种灵活性有这么几种应用:

  1. 可以对外隐藏返回类型的具体实现,适用于基于接口的框架
  2. 静态工厂方法所返回的对象的所属类,在我们编写静态方法时可以不用存在,这种灵活的静态工厂方法构成了服务提供者框架的基础,什么是服务提供者框架我们会在下面的内容中了解到。

优势四,可以简化代码

在创建参数化类型的实例时,可以简化我们的代码,例如:

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

如果我们的HashMap提供了如下的工厂方法:

1
2
3
4
5
public static <K, V> HashMap<K, V> newInstance() {
return new HashMap<K, V>();
}
```
那么我们前面的声明就可以改成:

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

1
不过在jdk1.7之后对与HashMap我们已经不需要这么做了,只需要如下声明即可:

HashMap<String, List> map = new HashMap<>();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
### 两个缺点
* 缺点一,类如果不包含公有的或者受保护的构造器就无法被子类化
* 缺点二,与普通的静态方法没有任何区别

### 服务提供者框架
* 服务接口:由服务提供者实现,用来抽象服务提供者提供的服务
* 服务提供者接口:可选,由服务提供者实现,用来抽象服务提供者。
* 提供者注册api:系统用来注册实现,让客户端访问他们的。
* 服务访问api:客户端用来获取服务实例的。

JDBC就是一个服务提供者框架的典型的例子,在JDBC中具体的实现如下:

#### 服务接口:Connection
Connection从字面意思上看就是一个连接,一个Connection表示着和一个数据库之间的连接或者一次会话,通过它,我们可以创建、执行Statement,并且获取Sql执行的结果。

#### 服务提供者接口:Driver
Driver通过如下方法生成一个Connection

public interface Driver {
···
Connection connect(String url, java.util.Properties info)
throws SQLException;
···
}

1
2
#### 提供者注册API:DriverManager.registerDriver()
服务提供者实现Driver类,通过DriverManager.registerDriver()方法将实例注册到DriverManager中。

public class DriverManager {
···
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {

    registerDriver(driver, null); 
}

···

public static synchronized void registerDriver(java.sql.Driver driver,
        DriverAction da)
    throws SQLException {

    /* Register the driver if it has not already been added to our list */
    if(driver != null) { 
        registeredDrivers.addIfAbsent(new DriverInfo(driver, da)); 
    } else {
        // This is for compatibility with the original DriverManager
        throw new NullPointerException(); 
    }

    println("registerDriver: " + driver); 
}
···

}

1
注册的动作是服务提供者完成的,在`mysql-connector-java`中存在如下的代码:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}

static {
    try {
        DriverManager.registerDriver(new Driver());
    } catch (SQLException var1) {
        throw new RuntimeException("Can't register driver!");
    }
}

}

1
根据上面的代码我们可以看到,当我们第一次加载`com.mysql.jdbc.Driver`的时候,也就是我们在执行如下代码的时候:

Class.forName(“com.mysql.jdbc.Driver”);

1
2
3
就是向DriverManager中注册一个Driver实例,这也就是为什么我们调用了上述代码后就可以通过DriverManager来获取一个Connection了。
#### 服务访问API:DriverManager.getConnection()
向`DriverManager`中注册了一个服务提供者实例`Driver`后,我们的客户端可以通过调用`DriverManager`中的`getConnection()`方法来获取一个数据库连接,当然我们通过`getConnection()`方法的源码可以发现实际实际上它也是通过调用`Driver`的`conect()`方法来创建一个新的连接。

public class DriverManager {

···

@CallerSensitive
public static Connection getConnection(String url,
    java.util.Properties info) throws SQLException {

    return (getConnection(url, info, Reflection.getCallerClass()));
}

···

@CallerSensitive
public static Connection getConnection(String url,
    String user, String password) throws SQLException {
    java.util.Properties info = new java.util.Properties();

    if (user != null) {
        info.put("user", user);
    }
    if (password != null) {
        info.put("password", password);
    }

    return (getConnection(url, info, Reflection.getCallerClass()));
}

···

//  Worker method called by the public getConnection() methods.
private static Connection getConnection(
    String url, java.util.Properties info, Class<?> caller) throws SQLException {
    /*
     * When callerCl is null, we should check the application's
     * (which is invoking this class indirectly)
     * classloader, so that the JDBC driver class outside rt.jar
     * can be loaded from here.
     */
    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 + "\")");

    // Walk through the loaded registeredDrivers attempting to make a connection.
    // Remember the first exception that gets raised so we can reraise it.
    SQLException reason = null;

    for(DriverInfo aDriver : registeredDrivers) {
        // If the caller does not have permission to load the driver then
        // skip it.
        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());
        }

    }

    // if we got here nobody could connect.
    if (reason != null)    {
        println("getConnection failed: " + reason);
        throw reason;
    }

    println("getConnection: no suitable driver found for "+ url);
    throw new SQLException("No suitable driver found for "+ url, "08001");
}

···

}
`

ps:“学习不止,码不停蹄”,如果你喜欢我的文章,就关注我吧。

扫码关注“代码无止境”