在 DI 之前


正如〈Inversion of Control?〉中談到的,控制、熟悉應用程式的流程走向,是很重要的一部份,這對於評估是否使用某個框架,瞭解換取而來的益處是否超越了犧牲掉的流程自由度是有幫助的,這才能使得框架選用具有意義。

BeforeDI 這個範例專案中,為了要能連線 H2 資料庫,在 cc.openhome.Main 寫了底下的流程:

Properties prop = new Properties();
prop.load(Main.class.getClassLoader().getResourceAsStream("jdbc.properties"));        

JdbcDataSource dataSource = new JdbcDataSource();
dataSource.setURL(prop.getProperty("cc.openhome.jdbcUrl"));
dataSource.setUser(prop.getProperty("cc.openhome.user"));
dataSource.setPassword(prop.getProperty("cc.openhome.password"));

AccountDAO acctDAO = new AccountDAOJdbcImpl(dataSource);
MessageDAO messageDAO = new MessageDAOJdbcImpl(dataSource);

UserService userService = new UserService(acctDAO, messageDAO);

userService.messages("caterpillar")
           .forEach(message -> {
               System.out.printf("%s\t%s%n",
                   message.getLocalDateTime(),
                   message.getBlabla());
           });

這個片段從類別路徑中的 jdbc.properties 中讀取設定,建立了 JdbcDataSource,作為 AccountDAOMessageDAO 建構實作物件之用,接著再將兩個 DAO,作為 UserService 的依賴物件,然後才使用 UserService 來做些操作。

物件的建立與相依注入,當然是建立應用程式時必要的關切點,只不過當過程太過冗長,模糊了商務流程之時,應該適當地將之分離。

就這個片段來說,真正的目的其實是取得 UserService 來做些操作,若想隔離相依注入這個關切點,也許建立一個工廠方法會比較好:

package cc.openhome;

import java.io.IOException;
import java.util.Properties;

import org.h2.jdbcx.JdbcDataSource;

import cc.openhome.model.AccountDAO;
import cc.openhome.model.AccountDAOJdbcImpl;
import cc.openhome.model.MessageDAO;
import cc.openhome.model.MessageDAOJdbcImpl;
import cc.openhome.model.UserService;

public class Service {
    public static UserService getUserService() throws IOException {
        Properties prop = new Properties();
        prop.load(Main.class.getClassLoader().getResourceAsStream("jdbc.properties"));      

        JdbcDataSource dataSource = new JdbcDataSource();
        dataSource.setURL(prop.getProperty("cc.openhome.jdbcUrl"));
        dataSource.setUser(prop.getProperty("cc.openhome.user"));
        dataSource.setPassword(prop.getProperty("cc.openhome.password"));

        AccountDAO acctDAO = new AccountDAOJdbcImpl(dataSource);
        MessageDAO messageDAO = new MessageDAOJdbcImpl(dataSource);

        UserService userService = new UserService(acctDAO, messageDAO);
        return userService;
    }
}

這麼一來,商務流程的部份,就可以藉由這個工廠方法來取得 UserService,不用在乎如何建立、相依注入等細節:

package cc.openhome;

import java.io.IOException;
import cc.openhome.model.UserService;

public class Main {
    public static void main(String[] args) throws IOException {
        UserService userService = Service.getUserService();

        userService.messages("caterpillar")
                   .forEach(message -> {
                       System.out.printf("%s\t%s%n",
                           message.getLocalDateTime(),
                           message.getBlabla());
                   });

    }
}

如此之來,程式碼的流程清晰了,而且即使是不懂 JDBC 或 DataSource 等的開發者,只要透過這樣的方式,也可以直接取得 UserService 進行操作。

上面的 Service 當然是特定用途,隨著打算開始整合各種程式庫或方案,你會遇到各種物件建立與相依設定需求,為此,你可能會重構 Service,使之越來越通用,像是可透過組態檔來進行相依設定,甚至成為一個通用於各式物件建立與相依設定的容器,實際上這類容器,在 Java 的世界中早已存在,且有多樣性的選擇,而最有名的實現之一就是 Spring 框架。