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