控制反轉(IoC)

 
 
 
大量使用工廠模式引發的問題:
 
    Client 對象須要使用 Service1 的 execute( ) 方法完成特定功能,而 Service1 的實現 Service1Impe類又依賴於 Service2的實現類 Service2Impl,爲了減小依賴,咱們爲 Service1 和 Service2對象的實例化分別提供工廠方法類的實現。
 
public interface Service1 {
    void execute();
}
 
public class Service1Impl implements Service1 {
 
    private Service2 service2;
 
    public Service1Impl(Service2 service2) {
        this.service2 = service2;
    }
 
    @Override
    public void execute() {
        System.out.println("Service1 is doing something.");
 
        service2.execute();
    }
 
}
 
public interface Service2 {
    void execute();
}
 
public class Service2Impl implements Service2 {
 
    @Override
    public void execute() {
        System.out.println("Service2 is doing something.");
    }
 
}
 
// 工廠類的實現
public class Service2Factory {
    public static Service2 getService2Instance() {
        return new Service2Impl();
    }
}
 
public class Service1Factory {
    public static Service1 getService1Instance() {
        Service2 service2 = Service2Factory.getService2Instance();
        return new Service1Impl(service2);
    }
 
}
 
// 測試代碼
public class Client {
    public void doSomething() {
        Service1 service = Service1Factory.getService1Instance();
        service.execute();
    }
}
-- Service1 is doing something.
-- Service2 is doing something.

 

 
大規模地使用工廠方法模式,會引發諸多問題:
  • 工廠類氾濫:在實際應用中,常常出現幾百個像這樣的服務類,若是使用工廠方法模式的話,就可能出現幾百個這樣的工廠類。由於不知道未來會產生多少子類擴展系統,因此也不能使用靜態工廠模式。
  • 依賴關係複雜:每每這幾百個 Service對象之間存在複雜的依賴關係,工廠類的裝配邏輯也隨之變得十分複雜。增長了裝配這些服務對象的精力,而不能專一於業務功能的開發。
  • 不易進行單元測試
 
Inversion of Control(控制反轉):
好萊塢原則:不要找咱們,咱們會找你。
咱們常常把控制邏輯寫在其餘地方而非客戶化的代碼裏,這樣就能夠更專一與客戶化的邏輯,即外部邏輯負責調用客戶化的邏輯。在軟件開發領域,取名叫:控制反轉(IoC)。
 
應用實例:
  • 模板方法模式——父類的模板方法控制子類的方法調用
  • 回調的方法也是控制反轉的很好應用。
  • 在 Java標準庫裏,查找(binarySearch) 和 排序(sort)這兩個方法,他們在執行過程當中會調用對象的 compareTo( ) 方法(若是這些對象實現了 java.lang.Comparable 接口的話),或者調用咱們所傳遞的回調接口 java.util.Comparator 對象的 compare( ) 方法來比較大小,最終完成查找/排序操做。
  • 框架:框架抽象了通用流程,咱們能夠經過框架提供的規範(如子類化抽象類或者實現相關的接口,實現相關的交互協議和接口等)就能夠把客戶化的代碼植入流程中,完成各類定製的需求。
 
框架和工具庫(Library)的區別是:若是框架沒有實現控制反轉,那麼框架就會退化爲工具庫。也就是說,使用工具庫,必須撰寫調用它們的邏輯,每次調用它們都會完成相應的工做,並把控制權返回給調用者;而框架不須要客戶程序撰寫控制調用的邏輯,由框架專門負責。
 
IoC 和 DI
    任何容器/框架都實現了控制反轉,它們所說的控制反轉指的是 對服務/對象/組件的實例化和查找實現了控制反轉,這只是控制反轉的一種而已。
 
實現控制反轉主要有兩種形式
  • Service Locator(服務定位器)
  • Dependency Injection(依賴注入,簡寫爲 DI)
 
Service Locator(服務定位器)
 
    服務定位器封裝了查找邏輯,隱藏了服務/對象/組件之間的依賴關係,爲它們提供了一個全局的入口。客戶對象只要依賴於它就能獲取想要的組件/服務/對象。
 
    工廠方法模式和服務定位器的區別是:服務定位器爲整個應用組件/服務/對象的獲取提供了單一的入口,而一個工廠只提供特定類型的實例化,若是一個工廠能提供全部組件/服務/對象的裝配和實例化,那它就被進化爲服務定位器。
 
    使用服務定位器時,容器/框架侵入了代碼,下降了代碼移植性,單元測試也相對比較麻煩。
    
 
 
 
    簡單的服務定位器:
//簡單的服務定位器
public class ServiceLocator {
    private static ServiceLocator locator;
    // 建立 HashMap<String, Object>對象來持有這些服務對象
    private Map<String, Object> services = new HashMap<String, Object>();
 
    // 初始化容器
    public static void configure() {
        load(new ServiceLocator());
 
        // Service一、Service2的對象實際上是按照單例建立的
        Service2 service2 = new Service2Impl();
        locator.services.put("service2", service2);
        Service1 service1 = new Service1Impl(service2);
        locator.services.put("service1", service1);
    }
 
    private static void load(ServiceLocator serviceLocator) {
        locator = serviceLocator;
    }
 
    public static Object lookup(String serviceName) {
        return locator.services.get(serviceName);
    }
 
    public static void registerService(String name, Object service) {
        locator.services.put(name, service);
    }
}

 

 
Client類的實現:
 
public class Client {
    public void doSomething() {
 
        Service1 service1 = (Service1) ServiceLocator.lookup("service1");
        service1.execute();
    }
 
    // 測試代碼
 
    public static void main(String[] args) {
        // 初始化容器
        ServiceLocator.configure();
 
        new Client().doSomething();
    }
}

 

 
 
Dependency Injection(依賴注入)
 
    外部程序把服務對象經過某種方式注入到客戶對象供其使用的方法稱之爲 依賴注入
根據 注入方式的不一樣,把依賴注入分爲 6類:
  1. Setter 注入:外部程序經過調用 setting方法爲客戶對象注入所依賴的對象。(Spring框架是使用反射機制調用 setting方法注入依賴的對象)
  2. Constructor 注入:經過帶參數的構造方法注入依賴對象。
  3. Annotation 注入:把實例化信息和對象之間的依賴關係信息使用 Annotation註解。
  4. Interface 注入:客戶程序經過實現容器/框架所規範的某些特殊接口,在爲客戶對象返回這些依賴的對象以前,容器回調這些接口的方法,注入所依賴的服務對象。如:Struts2中的 Action實現了接口 ServletRequesetAware、SessionAware等,只要 Action類實現這些接口,Struts框架就會調用這些接口的相應方法,注入 HttpServletRequest、HttpSession對象。
  5. Parameter 注入 :外部程序能夠經過函數參數,給客戶程序注入所依賴的服務對象。
  6. 其它形式的注入
 
    Setter 和 Constructor 是最多見的注入方式,代碼每每不會受到容器/框架的入侵,能夠在多種輕量級容器上移植,而其它方式或多或少都受到容器/框架代碼的入侵。
 
 
Interface 注入:(這種方式不夠靈活,容器必須預先定義一些接口實現注入,適合實現少數特定類型的對象注入)
 
// ServiceAware 接口
public interface ServiceAware {
    void injectService(Service service);
}
// Service 接口及其實現類
public interface Service {
    void excute();
}
public class ServiceImpl implements Service {
 
    @Override
    public void excute() {
        System.out.println("Service is doing something...");
    }
 
}
 
// 建立簡單的接口注入容器
public class InterfaceInjector {
 
    private static InterfaceInjector injector;
    private Map<Class, Object> services = new HashMap<Class, Object>();
 
    // 安裝容器
    public static void configure() {
        load(new InterfaceInjector());
    }
 
    public static <T> T getInstance(Class<T> clazz) {
        return injector.loadService(clazz);
    }
 
    private static void load(InterfaceInjector container) {
        InterfaceInjector.injector = container;
 
    }
 
    private <T> T loadService(Class<T> clazz) {
 
        Object service = injector.services.get(clazz);
 
        if (service != null) {
            return (T) service;
        }
 
        try {
            // 建立 clazz類實例
            service = clazz.newInstance();
            // 經過接口裝配依賴的對象,若是對象是 ServiceAware的實例爲真
            if (service instanceof ServiceAware) {
                // 則調用此接口的方法注入 ServiceAware對象
                ((ServiceAware) service).injectService(new ServiceImpl());
            }
            injector.services.put(clazz, service);
        } catch (Exception e) {
            e.printStackTrace();
        }
 
        return (T) service;
    }
 
}
 
// 只要服務類實現這個接口,容器就會注入 Service對象,定義一個 Client類實現該接口 
public class Client implements ServiceAware {
 
    private Service service;
 
    @Override
    public void injectService(Service service) {
        this.service = service;
    }
 
    public void doSomething() {
        service.excute();
    }
 
    public static void main(String[] args) {
        InterfaceInjector.configure();
        Client client = InterfaceInjector.getInstance(Client.class);
        client.doSomething();
    }
}
    -- Service is doing something...

 

 
 
Parameter 注入 :外部程序能夠經過函數參數,給客戶程序注入所依賴的服務對象。
//參數注入
public class Client {
    // 外部程序經過函數參數,給客戶程序注入所依賴的服務對象
    // 採用外部傳遞 Service對象方式,至於Service如何實例化,它一無所知
    public void dosomething(Service service) {
        service.excute();
    }
 
 
    // 使用方法:外部程序實例化一個 Service實例,傳遞給 Client的 dosomething(Service service)方法使用
    public static void main(String[] args) {
        Service service = new ServiceImpl();
        new Client().dosomething(service);
    }
}

 

注意:這種形式的注入比較特殊,Client 類的 dosomething(Service service) 方法不光完成了依賴的裝配,並且執行了 Service 回調的方法 execute( ),完成了其它邏輯。
 
Setter 和 Constructor 注入都是一種特殊的參數注入,它規定了只能使用 setting方法注入依賴 或者 只能對構造函數實現參數注入。
 
一些流行 DI 框架在使用參數注入實例化對象時,每每結合 Annotation注入、Interface 注入等方式一塊兒使用,可是明顯的一條,這些實現方法不該包含除依賴裝配以外的其它邏輯。
 
其它形式的注入:
    容器/框架提供一些高級用法,主要用來解決一些特殊問題。如:Spring框架的 lookup方法注入方式。使用場景:一個singleton的Bean須要引用一個prototype的Bean; 一個無狀態的Bean須要引用一個有狀態的Bean; ... ; 等等情景下,通常用的很少。
 
 
總結
  • IoC使得客戶專一於客戶化的邏輯設計,把程序流程的控制交給外部代碼,實現高內聚低耦合目標。
  • 把實例化的過程交給框架/容器來處理,使得咱們更專一於業務邏輯的開發。
相關文章
相關標籤/搜索