1. 程式人生 > >個人學習:Spring之控制反轉

個人學習:Spring之控制反轉

Spring的作用之一是控制反轉(依賴注入),要說清反轉控制需要依次說清三個問題
1.控制反轉的目的是什麼?
控制反轉的目的是——解耦

2.控制反轉是如何實現的解耦?
沒有控制反轉之前我們實現解耦的一個重要手段是使用工廠模式

先來看一下工廠模式,我們假設用戶向工廠購買不同的產品,工廠負責生產給使用者,用工廠模式實現的話,程式碼如下:

產品:

// 產品介面
public interface IProduct {
}

// 產品A
public class ProductA implements IProduct {
    ProductA() {
        System.out.println("產品A");
    }
}

// 產品B
public class ProductB implements IProduct {
    ProductB(){
        System.out.println("產品B");
    }
}

工廠:

//工廠介面
public interface IFactory {
    public IProduct production();
}

// 工廠A
public class FactoryA implements IFactory {
    // 多型:工廠A只能生產產品A
    private IProduct product = new ProductA();

    @Override
    public IProduct production() {
        return product;
    }
}

//工廠B
public class FactoryB implements IFactory {
    // 多型:工廠B只能生產產品B
    private IProduct product = new ProductB();

    @Override
    public IProduct production() {
        return product;
    }
}

使用者:

public class User {
    public static void main(String[] args) {
    // 購買產品A
    FactoryA fa = new FactoryA();
    fa.production();
		
    // 購買產品B
    FactoryB fb = new FactoryB();
    fb.production();
    }
}
應該說工廠模式已經很好的實現瞭解耦,當用戶需要新產品時,無需再修改原有的任何程式碼,只需要新增一個產品實現類和一個工廠實現類就可以輕鬆的實現擴充套件。
但是我們可以發現,工廠模式有一個缺陷:隨著產品種類的增加,我們需要增加同樣多種類的工廠

原因就在於這一段程式碼的耦合造成的:

// 工廠A只能生產產品A
private IProduct product = new ProductA();
// 工廠B只能生產產品B
private IProduct product = new ProductB();
我們來設想一下,目前的情況是,工廠的功能已經事先定義好了,使用者需要產品A,只能向A工廠要,需要產品B只能向B工廠要。
那麼能不能由使用者決定工廠的功能,當用戶需要產品A時,就要求工廠生產產品A,當用戶需要產品B時,就要求工廠生產產品B。
這樣我們就只需要一個工廠實現類,就可以獲得不同種類的產品,而且也無需更改現有程式碼,需要新產品時,只需要寫新的產品實現類就可以了。
也就是說:我們就要求程式實現這樣一種功能
//這裡我們不再規定某個工廠生產某個固定產品,而是在使用者程式模組中,根據需要改變工廠能夠生產的產品種類
private IProduct product;

這就是控制反轉的思想:程式的下游模組可以動態的改變程式上游模組的功能

3.Spring如何實現控制反轉?
經常的,我們把控制反轉又叫做依賴注入,在我看來,應該是為了實現控制反轉,而採用了依賴注入的方式
而Spring的核心功能之一就是依賴注入。Spring實現依賴注入的方式之一是:在工廠和使用者之間加入一層程式碼(即Spring容器),用來讀取外部的XML檔案中配置好的工廠型別的名字,並且例項化這個工廠類,存入Map中(Map<Sting,Object>),當用戶需要呼叫哪個工廠時,就去這個Map中取得對應名稱的哪個工廠例項。
所以本質上,針對不同產品的工廠,還是被例項化了,但是這不需要我們去寫一個個不同的工廠物件,而是通過外部XML資料注入資料,交由Spring容器來實現的。

程式碼解析:

產品:

// 產品介面
public interface IProduct {
}

// 產品A
public class ProductA implements IProduct {
    ProductA() {
        System.out.println("產品A");
    }
}

// 產品B
public class ProductB implements IProduct {
    ProductB(){
        System.out.println("產品B");
    }
}

工廠:

// 工廠介面
public interface IFactory {
	public IProduct production();
}

// 工廠例項:針對不同的產品,不再新增不同的工廠
public class Factory implements IFactory {
    // 不再指定具體產品
    private IProduct product;

    @Override
    public IProduct production() {
        return product;
    }
}

XML檔案:

<beans>
    <bean id="pa" class="com.****.ProducA" />
    <bean id="pb" class="com.****.ProducB" />
</beans>

模擬Spring容器:

//bean工廠介面:用來承載不同的工廠型別
public interface IBeanFactory {
    public Object getBean(String name);
}

// bean工廠例項:將所有工廠型別全部例項化到一個Map中
public class BeanFactory implements IBeanFactory {
    private Map<String, Object> beans = new HashMap<String, Object>();

    // 建構函式讀取XML檔案中的配置項,並例項化XML中規定的類,存入Map中
    public BeanFactory () {
    /** 為避免知識點過多造成理解困難,本段程式碼不展示,其具體功能是:
    1.通過JDOM或者DOM4J讀取XML檔案(需匯入jar包),獲得對應的工廠型別名稱(id)和class(不同工廠實現類的類名)
    2.通過Object o = Class.forName(factoryClass).newInstance();的方式構造不同的工廠類例項
    3.將工廠型別名稱(id)和工廠類例項(Object)存入Map<String,Object>,即程式碼中的beans中
    **/  
  }

    @Override
    public Map<String,Object> getBean(String name) {
    // 拿到Map,Map中經過以上的呼叫,已經生成了不同的bean物件和該物件的名稱
    return beans;    
    }
}

使用者:

public class User {
    public static void main(String[] args) {
        IBeanFactory factory = new BeanFactory();
        Product productA = (Product)factory.getBean("pa").get("pa");
        Product productB = (Product)factory.getBean("pb").get("pb");
    }
}
當我們需要新產品的時候,不需要再新增新的工廠型別例項,只需要實現新的產品例項,然後在XML文件中新增一條產品的配置即可。
這樣的情況可以被描述為:工廠對產品的依賴,不是一開始就寫好的程式碼,而是依靠外部XML中的配置項來動態注入的,這可以被稱為依賴注入

相關知識點:
1.工廠模式;
2.XML讀取,JDOM或DOM4J;
3.Class.forName(),呼叫類載入器載入某個類;
4.newInstance(),例項化類載入器載入的類,具有相同功能的還有new關鍵字,注意他們的區別;