工廠模式講解, 引入Spring IOC

炭燒生蠔發表於2019-04-25

引入

  • 假設有一個司機, 需要到某個城市, 於是我們給他一輛汽車
public class Demo {
    public static void main(String[] args) {
        Car car = new Car();
        car.run();
    }
}

public class Car {
    public void run(){
        System.out.println("汽車正在向前跑...");
    }
}
複製程式碼
  • 如果我們希望給到這個司機的始終是一輛車, 應該怎麼做? (單例)
  • 首先我們不能讓司機自己通過new產生一輛汽車, 而是應該通過呼叫Car類中的某個方法對外提供車.
public class Car {
    private static Car car = new Car();//用於提供給外界, 始終是同一輛車

    private Car(){};//私有構造方法, 在類之外不能通過new獲得本類物件了, 保證了單例

    public Car getInstance(){
        return car;
    }

    public void run(){
        System.out.println("汽車正在向前跑...");
    }
}

public static void main(String[] args) {
    Car car = Car.getInstance();
    car.run();
}
複製程式碼

 

簡單工廠

  • 下面考慮, 如果我們不希望只有汽車這種交通工具, 我們希望可以定製交通工具, 並定製生產交通工具的流程, 應該怎麼做?
  • 一旦產生由汽車到交通工具這樣的概念, 就應該想到多型. 我們可以定義一個Moveable介面, 在介面中宣告run()方法, 所有的交通工具類都實現該介面.
  • 對於定製生產流程, 我們可以通過一個工廠進行生產對應的交通工具.
public interface Moveable {
    void run();
}

public class Car implements Moveable{

    public Car(){};//私有構造方法, 在類之外不能通過new獲得本類物件了, 保證了單例

    public void run(){
        System.out.println("汽車正在向前跑...");
    }
}

public abstract class VehicleFactory {
    public abstract Moveable create();
}

public class CarFactory extends VehicleFactory {
    @Override
    public Moveable create() {
        return new Car();
    }
}

//Test
public static void main(String[] args) {
    VehicleFactory factory = new CarFactory();
    Moveable m = factory.create();
    m.run();
}
複製程式碼

 

抽象工廠

  • 下面把簡單工廠的畫面從腦海中清空, 講述另一種工廠實現.
  • 我們假設開頭的司機不是一個普通的司機, 他除了需要一種交通工具以到達某個城市外, 他還需要一把AK47, 並且還需要一個蘋果以備路上不時之需.
  • 所以我們需要給他一個工廠來製造這一系列產品.
  • 為了提高可擴充套件性, 我們還希望不同的工廠可以製作不同系列的產品, 比如上面說的A工廠製造的是汽車, AK47, 蘋果; 而B工廠製造的是飛機, 火箭炮, 旺仔小饅頭.
//test
public static void main(String[] args) {
    AbstractFactory factory = new Factory1();
    Vehiche v = factory.createVehiche();
    Weapon w = factory.createWeapon();
    Food f = factory.createFood();

    v.run();
    w.fire();
    f.eat();
}

public abstract class Vehiche {//交通工具的抽象類
    public abstract void run();
}

public abstract class Weapon {//武器的抽象類
    public abstract void fire();
}

public abstract class Food {//食物的抽象類
    public abstract void eat();
}

public class Car extends Vehiche{一種具體的交通工具
    @Override
    public void run() {
        System.out.println("小汽車啟動...");
    }
}

public class AK47 extends Weapon {//一種具體的武器
    @Override
    public void fire() {
        System.out.println("噠噠噠...");
    }
}

public class Apple extends Food{//一種具體的食物
    @Override
    public void eat() {
        System.out.println("大口吃蘋果...");
    }
}

//抽象工廠
public abstract class AbstractFactory {
    public abstract Vehiche createVehiche();
    public abstract Weapon createWeapon();
    public abstract Food createFood();
}

//抽象工廠的實現1
public class Factory1 extends AbstractFactory {
    @Override
    public Vehiche createVehiche() {
        return new Car();
    }

    @Override
    public Weapon createWeapon() {
        return new AK47();
    }

    @Override
    public Food createFood() {
        return new Apple();
    }
}
複製程式碼

 

  • 總結一下, 抽象工廠和簡單工廠各有什麼優劣?
  • 抽象工廠能夠生產一系列產品, 也能方便地替換掉一系列產品, 但是如果想要在產品系列中新增多一個品種將會非常麻煩. 比如說在上面的系列產品中新增一個盔甲抽象類, 那麼抽象工廠以及對應的實現都要修改原始碼了.
  • 而簡單工廠能夠靈活的生產但一個品種的產品, 但是如果生產的品種較多, 會出現工廠氾濫的問題.
  • 兩者優劣互補, 那麼有沒有可以相容兩者優點的工廠實現呢? 下面看spring的工廠實現, 它給出了一種解決方案.

 

Spring的bean工廠

  • 我們再次考慮最原始的情況, 有一個Moveable介面, 裡面有run方法, Car小汽車類實現了該介面.
public static void main(String[] args) {
    Moveable m = new Car();
    m.run();
}

public interface Moveable {
    void run();
}

public class Car implements Moveable{
    @Override
    public void run() {
        System.out.println("小汽車往前跑...");
    }
}
複製程式碼
  • 在Spring的bean工廠中, 新物件不是通過new關鍵字獲取的, 而是通過配置檔案獲取的.
  • 具體的過程是: 先讀取配置檔案獲得該類的class物件, 然後通過class物件建立具體的例項物件.
public static void main(String[] args) throws Exception {
    //獲取配置檔案
    Properties props = new Properties();
    props.load(Test.class.getClassLoader().getResourceAsStream("spring.properties"));
    //獲取配置檔案中配置的類
    String vehicheTypeName = props.getProperty("vehicheTypeName");
    //反射生成對應的物件
    Moveable m = (Moveable) Class.forName(vehicheTypeName).newInstance();
    m.run();
}

//spring.properties
vehicheTypeName=designPattern.factory.springFactory.Car
複製程式碼

 

  • 上面是對spring中bean工廠使用的模擬, 下面我們使用真實的spring來生成Car物件, 對比一下.
public static void main(String[] args) throws Exception {
    BeanFactory bf = new ClassPathXmlApplicationContext("applicationContext.xml");
    Vehiche v = (Vehiche)bf.getBean("v");
    v.run();
}

//配置檔案
<bean id="v" class="designPattern.factory.Car">
</bean>
複製程式碼
  • 經過對比我們發現我們自己寫的簡單工廠和spring的bean工廠在使用上沒有什麼區別, 確實spring使用起來就是這麼簡單, 下面我們模擬一下spring的bean工廠實現.

 

模擬Spring工廠實現

模擬IOC

  • 都說spring是個bean容器, 以下的程式碼將展示它是如何生成bean, 並把bean放入容器中供使用者獲取的.
  • 思路比較簡單:
  1. 建立BeanFactory工廠介面, 新增方法getBean().
  2. 建立BeanFactory的實現類ClassPathXmlApplicationContext. 將在該實現類中展示IOC的具體實現.
  3. ClassPathXmlApplicationContext需要一個container容器存放建立的bean物件, 這裡使用HashMap實現.
  4. ClassPathXmlApplicationContext的構造方法中讀取spring的配置檔案, 這裡使用到了dom4j. 讀取配置檔案後根據beanclass屬性使用反射建立出bean物件. 然後把idbean物件分別作為keyvalue新增到容器中.
  5. 當工廠被呼叫getBean()方法時, 從容器中找到對應的bean並返回.
public static void main(String[] args) throws Exception {
    BeanFactory bf = new ClassPathXmlApplicationContext("applicationContext.xml");
    Vehiche v = (Vehiche) bf.getBean("v");
    v.run();
}

//BeanFactory的實現類
public class ClassPathXmlApplicationContext implements BeanFactory {

    private Map<String, Object> container = new HashMap<>();//用於存放bean物件的容器
    
    //在構造方法中讀取xml配置檔案, 把bean物件都建立好並放入容器中
    public ClassPathXmlApplicationContext(String propAddr) throws Exception {
        SAXReader reader = new SAXReader();
        File file = new File(this.getClass().getClassLoader().getResource(propAddr).toURI());
        Document document = reader.read(file);
        Element root = document.getRootElement();
        List<Element> childElements = root.elements();

        for (Element child : childElements) {
            Object bean = Class.forName(child.attributeValue("class")).newInstance();
            container.put(child.attributeValue("id"), bean);
        }
    }

    @Override
    public Object getBean(String beanId) {
        return container.containsKey(beanId) ? container.get(beanId) : null;
    }
}

//極簡BeanFactory
public interface BeanFactory {
    Object getBean(String beanId);
}

//xml中配置的bean
<bean id="v" class="designPattern.factory.Car">
</bean>
複製程式碼

相關文章