应用抽象工厂模式自己动手写一个ioc
本文的作者Danny hui似乎是TTS上的新人,我從Google搜不出一點關于本人的信息。從通過本文可以看出他對模式與IoC有自己獨到的見解,本文在TTS上引發很多網友回帖,反響不一。那么我們現在來看看作者的IoC之路吧。
?
?原文:http://www.theserverside.com/tt/articles/article.tss?l=InjectionwithAbstractFactory
?
簡介
?
本文重點討論的是DI(依賴注入)結合設計模式中的Abstract Factory(抽象工廠模式)的優勢與弊端。該方式尤其適合以下場合:
l?????????通過dynamic parameters(動態參數)來建立一個local stateful對象
l?????????當創建對象時拋出了checked exception時進行相應處理,
l?????????動態的封裝對象
像Spring IoC容器,PicoContainer以及Guice都無法圓滿的解決這些問題或者說它們幾乎做不到!!!
?
用Abstractory Factory模式來實現DI
?
?
?
現在通過下面兩種途徑來改進經典GoF中的Abstract Factory:
l?????????一個工廠接口來代替抽象工廠類(可行)
l?????????每一個工廠方法的職責就是創建對象,并為其注入依賴
?
?
來看一個簡單的例子吧:
?
?
在這里,ComponentA依賴于ComponentB。為方便進行單元測試ComponentAImpl類,接口ComponentB的實現必須注入到ComponentAImpl中去。下面看看采用Abstract Factory模式完成依賴注入的Java實現代碼:
?
?//Abstract Factory for Dependency Injection
//Factory interface
public interface Module1ServiceFactory {
?ComponentA getComponentA();
?ComponentB getComponentB();
}
//Concrete factory
public class Module1ServiceFactoryImpl implements Module1ServiceFactory {
?private ComponentA componentA;
?private ComponentB componentB;
?private Module1Servicefactory instance;
?private Module1ServicefactoryImpl() {}
?public static synchronized Module1ServiceFactory getInstance() {
??if (null == instance) {
???instance = new Module1ServicefactoryImpl();
???componentA = new ComponentAImpl();
???componentB = new ComponentBImpl();
???componentA.setComponentB(componentB);
??}
??return instance;
?}
?public ComponentA getComponentA() {
??return componentA;
?}
?public ComponentB getComponentB() {
??return componentB;
?}
}
//Client
public class Client {
?public static void main(String[] args) {
??Module1ServiceFactory m1sf =
???Module1ServicefactoryImpl.getInstance();
??ComponentA componentA = m1sf.getComponentA();
??componentA.operationA1();
?}
}
?
?
?
?
?
延遲加載
?
延遲加載對象可以通過改變方法來實現,比如說我現在要稍微對getComponentA()進行改動,請看:
?
?public synchronized ComponentA getComponentA() {
??if (null == componentA) {
???componentA = new ComponentAImpl();
??}
??return componentA;
?}
?
當然我們的Module1ServiceFactoryImpl.getInstance()方法也要進行相應的改動了,我們可以通過傳遞一個參數來判斷Module1ServiceFactoryImpl.getInstance()是否需要創建對象。
?
?
?
非Singleton作用域
?
上面的代碼僅僅只是建立singleton對象。如果需要在每次調用getComponentA()和getComponentB()的時,都返回新創建的對象的話,我們可以對我們的Abstract Factory進行下面的改動:
?
//Concrete factory
public class Module1ServiceFactoryImpl {
?private Module1ServiceFactory instance;
?private Module1ServiceFactoryImpl() {}
?public static synchronized Module1ServiceFactory getInstance() {
??if (null == instance) {
???instance = new Module1ServiceFactoryImpl();
??}
return instance;
?}
?public ComponentA getComponentA() {
??ComponentA componentA = new ComponentAImpl();
??ComponentB componentB = getComponentB();
??componentA.setComponentB(componentB);
??return componentA;
?}
?public ComponentB getComponentB() {
??return new ComponentBImpl();
?}
}
?
?
?
?
?
類似的,我們還可以將一個singleton對象注入到非singleton對象中去。比如說,我們假設ComponentB此時是singleton,ComponentA為非singleton,那么我們可以這樣:
?
//Concrete factory
public class Module1ServiceFactoryImpl {
?private Module1ServiceFactory instance;
?private ComponentB componentB;
?private Module1ServicefactoryImpl() {}
?public static synchronized Module1ServiceFactory getInstance() {
??if (null == instance) {
???instance = new Module1ServiceFactoryImpl();
???componentB = new ComponentBImpl();
??}
return instance;
?}
?public ComponentA getComponentA() {
??ComponentA componentA = new ComponentAImpl();
??componentA.setComponentB(componentB);
??return componentA;
?}
?public ComponentB getComponentB() {
??return componentB;
?}
}
?
將一個非singleton對象注入到singleton對象也不是做不到,但這種應用場合在現實世界中是非常罕見的。盡管如此,但在使用dynamic parameters賦級一個locallocal變量時,創建一個非singleton對象卻很普遍。接下的話題,我們就談談這個。
?
?
?
用dynamic parameters為singleton創建一個local stateful對象
?
這是所有IoC框架所面臨的問題。下面的代碼中,仍然假定ComponentA為singletion:
?
?
??public void operationA2() {
??String s = aPrivateMethod();
??int i = anotherMethod();
??ComponentC componentC = new ComponentCImpl(s, i);
??//do something else.
?}
?
?
這里,ComponentAImpl用到了ComponentC接口。雖然,為了更IoC,我們需要將它注入ComponentC的實現,而不是直接硬編碼在ComponentAImpl.operationA2()方法中去,這樣做還有一個好處就是,方便單元測試。但問題來了,我們不能將ComponentC作為一個實例變量,因為它是有狀態的(stateful),它維持著某一個特定的客戶端狀態,不能與其它客戶端進行共享。因此,不能使用常用的setter或construtctor注入方法來實現。
?
使用Abstract Factory模式的話,有兩個方法可以解決這個問題。不過,都得改動Module1ServiceFactory接口,添加下面方法:
?
??ComponentC getComponentC(String s, int i);
?
?
請看我在Module1ServiceFactoryImpl中的實現代碼:
?
public ComponentC getComponentC(String s, int i) {
??return new ComponentCImpl(s, i);
?}
??
?
第一種方法就是將包含它的“工廠”注入到所需的local stateful對象中去:
?
?private Module1ServiceFactory factory;
?public void setModule1ServiceFactory(Module1ServiceFactory factory) {
??this.factory = factory;
?}
//ComponentAImpl.operationA2() becomes:
?public void operationA2() {
??String s = aPrivateMethod();
??int i = anotherMethod();
??ComponentC componentC = factory.getComponentC(s, i);
??//do something else.
?}
?
?
?
?
缺點顯而易見:ComponentAImpl現在與Module1ServiceFactory綁定在一起了,如果要對ComponentAImpl進行單元測試的話,我們不得不mock一個Module1ServiceFactory實現。縱然有這個缺陷,但直接為stateful對象注入“工廠”對象的方法也最為簡單。類似的技術廣泛的在J2EE領域采用,比如說JPA,將我們將entity manager factory可以注入到應用程序代碼后,它就專門負責管理自身創建的application-managed entity。(注:如果熟悉Hibernate的話,也可以將entity manager factory想象成HibernateSessionFacory,?application-managed entity想象成Session)
?
?
第二種方法就是將方法抽出,移到抽象類中去,便于單元測試:
?
?
?
?public abstract class AbstractComponentA implements ComponentA {
public void operationA2() {
??String s = aPrivateMethod();
??int i = anotherMethod();
??ComponentC componentC = getComponentC(s, i);
??//do something else.
?}
?public abstract ComponentC getComponentC(String s, int i) ;
}
public class ComponentAImpl extends AbstractComponentA {
?public ComponentC getComponentC(String s, int i) {
??return new ComponentCimpl(s, i);
?}
}
?
?
這種方式類似于Springframework的方法注入(Metod Injection),不過Spring不需要傳遞dynamic parameter也能創建stateful對象。此時,單元測試可以不需要再實現任何mock工廠。但是,這仍然是一個笨拙的辦法。設想一下,我們的類里如果有10個這樣的local stateful對象,那么我們需要提供10個抽象方法,才能再次讓單元測試變得簡單,可是代價就造成是更加混亂的應用程序代碼。
?
Springframework還可以通過使用Java反射機構還解決類似問題。但這更加復雜了,并且不適合正常應用程序編碼工作。
?
?
處理創建對象時拋出的checked exceptions
?
這個問題也是讓IoC容器頭痛的。如果checked exception在對象創建時拋出,應用程序可能希望是能捕獲并且能夠恢復。我們來看一下這個關于Web Service的需求實例:當客戶端嘗試建立web service的stub時,并且此時服務端web service還不可用,那么客戶端是能夠捕獲stub所拋出的異常,然后顯示相應信息,并詢問用戶是否稍后繼續再次連接。如果單純用IoC容器的話,拋出具體指定的checked exception是很困難的。而手工代碼卻可以很輕松的解決這個問題——我們可以簡單的將“工廠”的checked exceptions拋出,留給應用程序代碼去手工處理或者恢復它們。
?
?
動態封裝對象
很多場合下,一個接口對應著多個不同的實現類,類型實例就是設計模式中的策略模式。那么,用一個參數就可以決定具體哪個實現類應該被注入到相應的封裝對象中去。然而使用基于XML和IoC容器是靜態封裝對象,很難實現此功能。也許編程式的IoC容器可以解決動態依賴問題,但我要說的是我們的Abstract Factory則更簡單直接,看看下面的代碼就知道了:
?
?
?
?
?//Concrete factory
public class Module1ServiceFactoryImpl {
?...
?public ComponentA getComponentA(int strategy) {
??ComponentA componentA = new ComponentAImpl();
??ComponentB componentB = getComponentB(strategy);
??componentA.setComponentB(componentB);
??return componentA;
?}
?public ComponentB getComponentB(int strategy) {
??switch(strategy) {
???case STRATEGYA:
????return new StrategyA();
???case STRATEGYB:
????return new StrategyB();
???default:
????return null;
??????????????????????? }
?}
}
?
?
?
注意這里StrategyA和StrategyB共享實現了ComponentB接口。
?
?
?
?
結束語
?
今天我們談到的運用依賴注入和Absratct Factory設計模式來解決下列問題:
1.???通過動態參數,創建local stateful對象
2.???處理創建對象時拋出的checked exceptions
3.???動態封裝注入對象
?
除此以外,該方法與其它IoC容器相比,性能更好,畢竟是直接硬編碼嘛。那么最大的缺點自然就是要手工寫很多基礎代碼了,并且如果要延遲加載與主動加載之間來回切換的話,代碼的改動量是很可觀的。不過呢,這樣的需求幾乎是不存在的。
?
可單元測試的關鍵點是基于接口而非實體類編程。這樣的話mock對象可以注入到任何需要注入的地方去。
?
不管怎么樣,有時候在我們的應用程序中,依賴注入是一個不得不解決的問題。所有IoC容器以及手工的依賴注入解決方案都是專注于各自的領域——Spring IoC使用XML配置,Google Guice使用特殊的Java,我們的Abstract Factory也是如此。通過這些解決方案,我們可以避免應用程序中到處顯現依賴的編碼,類與類之間耦合度降低,使用Mock對象就可以正常進行單元測試。
對所有的IoC容器來說,無論是聲明式的還是編程式的,它們的目的就是作為一個對象創建和封裝工廠。同樣我們的Abstract Factory也是如此,只不過這是一個可定制的依賴注入解決方案。
最后,使用IoC容器,我們可以消除類與類之間的依賴,從而讓單元測試變得更加簡單。但代價就是你不得不再次依賴于第三方Jar包或XML配置文件。IoC容器并沒有一個統一的標準,每個框架所提供的特性和功能都是不同的,因為一旦你使用了某個IoC框架,就意味
總結
以上是生活随笔為你收集整理的应用抽象工厂模式自己动手写一个ioc的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 抽象工厂+反射+依赖注入 实现对数据访问
- 下一篇: 将数据库的操作代码从servlet中剥离