1. 程式人生 > 實用技巧 >工廠模式-理解Spring的Bean工廠(馬士兵經典例子)

工廠模式-理解Spring的Bean工廠(馬士兵經典例子)

工廠模式-理解Spring的Bean工廠

接面向物件裡面 “老張開車去東北”的場景。連結名稱

封裝“老張開車去東北”裡面的交通工具,封裝交通工具Car

只給司機一輛車(單例、多例)

順帶講解單例

要求只能有一輛車,別人不能new Car,只有Car自己能控制newCar的邏輯。私有化構造方法,別人就不能new了。

/**
 * 交通工具Car
 * 
 */
public class Car {
	
	//private static Car car = new Car();
	private Car(){}
	
	public static Car getInstance(){
		return new Car();
	}
	
	public void run(){
		System.out.println("冒著煙奔跑中...");
	}

}

工廠就是自主生產自己的產品,不再依賴於new。比如你想new我家的一個抽屜,你想拿錢就拿錢, 肯定不行。

但是我要給你提供一個方法:getChouTi(); 我就能在get方法裡面做各種各樣的限制了。

比如返回Car的getInstance方法,可以做邏輯判斷。

/**
 * 交通工具Car
 */
public class Car {

    private Car(){}

    public static Car getInstance(){
        if(有駕照){
            return new Car();
        }
        return null;
    }
    public void run(){
        System.out.println("冒著煙奔跑中...");
    }
}

再回到上面的要求,只有一輛車,這麼做:自己new一個Car,呼叫getInstance時候,返回這個Car 。

public class Car {
	
	private static Car car = new Car();
	private Car(){}
	
	public static Car getInstance(){
		return car;
	}
	
	public void run(){
		System.out.println("冒著煙奔跑中...");
	}

}

測試

getInstance兩次看是不是一輛車:

public static void main(String[] args) {
        Car car1 = Car.getInstance();
        Car car2 = Car.getInstance();
        System.err.println(car1 == car2);
    }

列印:true

列印true,說明是一輛車

這個模式叫 單例,又有人叫這個getInstance方法叫靜態工廠方法。

任何方法,裡面控制了產生物件的邏輯,都可以叫工廠方法。

多例

如果Car類裡面返回的不是一個Car,裡面有一個List裝了一堆的Car,getInstance的時候隨機返回一個,這個又有人起了個名字 叫---多例

public class Car {
	
	//private static Car car = new Car();
	private static List<Car> cars = new ArrayList<>();
	
	static{
		//靜態初始化cars
		cars.add(new Car());
		cars.add(new Car());
	}
	
	private Car(){}
	
	public static Car getInstance(){
		//return car;
		//隨機返回一個Car,這裡就不隨機了
		return cars.get(1);
	}
	
	public void run(){
		System.out.println("冒著煙奔跑中...");
	}

}

JDBC連線池,裡面裝的Connection,就是多例。

任意定製交通工具的型別和生產過程

自然就想起了多型,抽取一個介面:Moveable,然後讓Car實現Moveable介面:

public interface Moveable {
	void run();
}

Car的實現:

public class Car implements Moveable{
	@Override
	public void run() {
		System.out.println("冒著煙奔跑中...");
	}
}

飛機的實現:

public class Plane implements Moveable{

	@Override
	public void run() {
		System.out.println("扇著翅膀飛呀飛...");
	}
}

測試類:

呼叫的時候,父類引用指向子類物件,多型,我new誰,就呼叫的是誰,很隨意就換了交通工具:

Moveable m = new Car();
m.run();
m = new Plane();
m.run();
列印結果:
冒著煙奔跑中...
扇著翅膀飛呀飛...

還有其他任何交通工具 比如交通工具是哈利波特的掃帚,就直接實現Moveable介面,你就可以直接new Broom(); 了 。

現在存在的問題就是,可以任意的new 交通工具,構造方法是公開的。現在想對任意交通工具的生產過程也能夠定製的話。有了上面單例的思路,現在第一個想到的還是,在交通工具類裡面寫一個靜態的方法控制new 的過程。這裡比如飛機,把產生飛機的過程單獨一個類拿出來,比如叫飛機工廠PlaneFactory:

//飛機工廠類
public class PlaneFactory {
	public Plane createPlane(){
		//單例、多例、條件檢查自己控制
		return new Plane();
	}
}

測試 :

//飛機工廠
		PlaneFactory factory = new PlaneFactory();
		Moveable m = factory.createPlane();
		m.run();

列印結果:
扇著翅膀飛呀飛...

如果現在想有一個Car工廠,那麼很簡單,就是這樣:

//Car工廠類
public class CarFactory {
	public Car createPlane(){
		//單例、多例、條件檢查自己控制
		return new Car();
	}
}

測試程式碼:

以前是開著飛機,現在想換成開Car,需要把飛機工廠換成了Car工廠,呼叫他的createCar方法。這樣太彆扭了。

//飛機工廠
		//PlaneFactory factory = new PlaneFactory();
		//換成Car工廠,整個工廠方法都得換
		CarFactory factory = new CarFactory();
		Moveable m = factory.createCar();
		m.run();

有沒有什麼辦法,從飛機換成Car的時候,只換工廠的實現就行呢?自然就想起了多型,有多型就得有父類、子類。所以工廠類需要有一個父類。Factory本來是產生交通工具的,抽象出一個產生交通工具的工廠:

//交通工具工廠
public abstract class VehicleFactory {

	//具體生成什麼交通工具由子類決定,這裡是抽象的。
	public abstract Moveable create();
}

這時候讓CarFactory和PlaneFactory去繼承VehicleFactory :

//Car工廠類
public class CarFactory extends VehicleFactory{
	
	@Override
	public Moveable create() {
		//單例、多例、條件檢查自己控制
		return new Car();
	}
}
//飛機工廠類
public class PlaneFactory extends VehicleFactory {
	
	@Override
	public Moveable create() {
		//單例、多例、條件檢查自己控制
		return new Plane();
	}
}

換了工廠的實現,就可以換交通工具了,比如你加了一個哈利波特的魔法掃帚,需要加一個Broom類實現Moveable介面,和一個BroomFactory工廠類繼承VehicleFactory就可以了。

//掃帚
public class Broom implements Moveable{

	@Override
	public void run() {
		System.out.println("掃帚搖著尾巴呼呼呼...");
	}
}

//掃帚工廠類
public class BroomFactory extends VehicleFactory {
	
	@Override
	public Moveable create() {
		//單例、多例、條件檢查自己控制
		return new Broom();
	}
}

此時測試程式碼就成了這樣子:

//機車工廠,new飛機工廠例項
		VehicleFactory factory = new PlaneFactory();
		Moveable m = factory.create();
		m.run();
		//換成Car工廠
		factory = new CarFactory();
		m = factory.create();
		m.run();
		//換成掃帚工廠
		factory = new BroomFactory();
		m = factory.create();
		m.run();
列印結果;
扇著翅膀飛呀飛...
冒著煙奔跑中...
掃帚搖著尾巴呼呼呼...

在某一個維度上有了可擴充套件了,不僅可以控制產生交通工具的型別,還可以控制產生交通工具的生產過程。需要改的只有一個地方:要是需要換交通工具,站在客戶角度,需要改的只有交通工具的工廠,其他地方都不用動。如果用了配置檔案的話,程式碼都不用該,只改配置檔案即可。後面再說。

關於抽象類和介面的選擇:

比如上面的交通工具工廠,是一個抽象類,也可以設計成介面,麼問題。

假如這個概念在我們腦子是確確實實存在的,就用抽象類,

假如這個概念只是某些方面的特性:比如會飛的,會跑的,就用介面

假如兩個概念模糊的時候,不知道選擇哪個的時候,就用介面,原因是,從實現了這個介面後,還能從其它的抽象類繼承,更靈活。

抽象工廠

看一下JDK裡面,先看getInstance方法,有一大堆,不同類裡面有同樣的方法getInstance。

這些大多數都是靜態的工廠方法,是不是單例不一定,得看具體的實現。

各種各樣的Factory也很多。像下面的加密的key,就不適合new,用一個工廠去實現它,產生的時候可以實現各種各種演算法,檢測各種資質,new的話構造方法只能是寫死的,用工廠的話,實現一個子類的時候還可以控制生產過程,更靈活。

總而言之,getInstance和Factory,JDK裡面很常用。下面開始說抽象工廠。

回到最原始的狀態,我們有一輛車Car。

控制一系列的產品(車、武器、食品補給)

現在讓這個人,開著車,拿著AK47,吃著蘋果。 意思就是,這是一些列的產品,要控制這一些列的產品的生產。

比如你要裝修,海爾整體廚房,有微波爐,油煙機,洗衣機,電磁爐。一系列的產品。

public class Car{
	
	public void run() {
		System.out.println("冒著煙奔跑中...");
	}
}

public class AK47 {

	public void shoot(){
		System.out.print("噠噠噠....");
	}
}
public class Apple {
	public void getName(){
		System.out.println("Apple..."); 
	}
}

測試類:

Car car = new Car();
		car.run();
		AK47 ak = new AK47();
		ak.shoot();
		Apple apple = new Apple();
		apple.getName();
列印:
冒著煙奔跑中...
噠噠噠....
Apple...

產生這一系列的產品,需要有一個預設的工廠:

//預設的工廠
public class DefaultFactory {

	public Car createCar(){
		return new Car();
	}
	public AK47 createAK47(){
		return new AK47();
	}
	public Apple createApple(){
		return new Apple();
	}
}

此時的測試程式,只要new出來一個預設工廠,就可以生產這一系列的產品了:

DefaultFactory factory = new DefaultFactory();
		Car car = factory.createCar();
		car.run();
		AK47 ak = factory.createAK47();
		ak.shoot();
		Apple apple = factory.createApple();
		apple.getName();
列印:
冒著煙奔跑中...
噠噠噠....
Apple...

當需要吧這一系列產品全換掉的話,把這個工廠換掉就可以了:

這裡新建一個工廠,魔法工廠:

//哈利波特的魔法工廠
public class MagicFactory {

	//交通工具:掃把
	public Broom createBroom(){
		return new Broom();
	}
	
	//武器:魔法棒
	public MagicStick createMagicStick(){
		return new MagicStick();
	}
	//食物:毒蘑菇
	public MushRoom createMushRoom(){
		return new MushRoom();
	}
}

//掃帚
public class Broom{

	public void run() {
		System.out.println("掃帚搖著尾巴呼呼呼...");
	}
}


//武器:魔法棒
public class MagicStick {
}

//食物:毒蘑菇
public class MushRoom {

}

但是此時,站在客戶的角度,想把工廠從DefaultFactory換到MagicFactory:

有了之前的經驗,這裡自然就聯想到,工廠建立的不能是具體的類,要是一個介面/或者是一個抽象類,比如你的Car,工廠建立的不能是Car,要是Car的父類,這樣在換工廠的時候,下面的程式碼才可以不用改動。所以,我們要建一個抽象工廠,然後讓DefaultFactory、MagicFactory都去繼承/實現這個工廠。而且工廠的返回值,都是抽象類或者介面。

//抽象工廠
public abstract class AbstractFactory {
	//生產 交通工具
	public abstract Vehicle createVehicle();
	//生產 武器
	public abstract Weapon createWeapon();
	//生產食物
	public abstract Food createFood();
}


//交通工具
public abstract class Vehicle {
	//實現由子類決定
	public abstract void run();
}

//食物
public abstract class Food {
	public abstract void printName();
}


//武器
public abstract class Weapon {
	//
	public abstract void shoot();
}

產品類:都繼承產品的抽象類

public class Car extends Vehicle{
	@Override
	public void run() {
		System.out.println("冒著煙奔跑中...");
	}
}
//掃帚
public class Broom extends Vehicle{

	@Override
	public void run() {
		System.out.println("掃帚搖著尾巴呼呼呼...");
	}
}
//食物:毒蘑菇
public class MushRoom extends Food {

	@Override
	public void printName() {
		System.out.println("mushroom");
	}

}

public class Apple extends Food {
	@Override
	public void printName() {
		System.out.println("apple");
	}
}

public class AK47 extends Weapon{

	public void shoot(){
		System.out.println("噠噠噠....");
	}
}

//武器:魔法棒
public class MagicStick extends Weapon {
	@Override
	public void shoot() {
		System.out.println("fire hu hu hu ...");
	}

}

魔法工廠繼承抽象工廠:

//哈利波特的魔法工廠
public class MagicFactory extends AbstractFactory {

	//交通工具:掃把
	public Vehicle createVehicle(){
		return new Broom();
	}
	
	//武器:魔法棒
	public Weapon createWeapon(){
		return new MagicStick();
	}
	//食物:毒蘑菇
	public Food createFood(){
		return new MushRoom();
	}
}

DefaultFactory繼承抽象工廠:

//預設的工廠
public class DefaultFactory extends AbstractFactory{

	@Override
	public Food createFood() {
		return new Apple();
	}

	@Override
	public Vehicle createVehicle() {
		return new Car();
	}

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

最終形成的類結構是這樣的:

測試程式:

//換一個工廠,只需要改動這一處,就可以了,換一個工廠,就把生產的系列產品都換了
		AbstractFactory factory =  new MagicFactory(); //new DefaultFactory();
		//換一個工廠
		Vehicle vehicle = factory.createVehicle();
		vehicle.run();
		Weapon weapon = factory.createWeapon();
		weapon.shoot();
		Food food = factory.createFood();
		food.printName();
	
DefaultFactory列印:
冒著煙奔跑中...
噠噠噠....
apple

**把工廠換為****MagicFactory

AbstractFactory factory = new DefaultFactory();

列印:

掃帚搖著尾巴呼呼呼...

fire hu hu hu ...

mushroom

抽象工廠:生產一系列的產品,如果你想換掉一系列的產品,或者你想在這一系列的產品上進行擴充套件,以及想對這一些列產品的生成過程進行控制,用抽象工廠。

抽象工廠和普通工廠的優缺點:

普通工廠:

可以在產品的維度上進行擴充套件,可以產生新的產品,可以產生新的產品的工廠。也就是說可以在產品的維度進行擴充套件。

在普通工廠想要產生產品系列,就會特別麻煩。產生一個產品,就會出現一個產品的Factory。就會出現”工廠氾濫”。

抽象工廠:

能換工廠,生產新的產品系列,但是不能產生新的產品品種,新新增一個產品,就要在抽象工廠裡面加 createXXX(); 方法,所有子類都要實現這個方法。要改動的地方太多。

有沒有一種工廠,結合普通工廠和抽象工廠的有點呢?

既可以隨意新增產品品種,又很方便的新增產品系列? 沒有。

Spring提供了一種方案,Spring的Bean工廠。

Spring說,你就不要這麼Moveable m = new Car(); 就行new Car了,你給配置到配置檔案裡。

測試java讀取properties配置檔案反射生成物件

spring.properties:

VehicleType=com.lhy.springfactory.Car

測試程式碼:

public static void main(String[] args) throws Exception{
	Properties props = new Properties();
		props.load(Test.class.getClassLoader().getResourceAsStream("com/lhy/springfactory/spring.properties"));

		String vehicleTypeName = props.getProperty("VehicleType");
		System.out.println(vehicleTypeName);
		//反射生成物件
		Object o = Class.forName(vehicleTypeName).newInstance();
		Moveable m = (Moveable)o;
		m.run();
	}
列印結果:
com.lhy.springfactory.Car
冒著煙奔跑中...

把配置檔案換成或者Trian:

VehicleType=com.lhy.springfactory.Train

執行測試程式碼,列印:

com.lhy.springfactory.Train

小火車嗚嗚嗚...

可以看到,只是改了配置檔案,就可以動態控制生成的類了。程式碼都不用動。Spring就是這樣的思路。

最簡單的使用Spring:

引入必須的jar包,

Spring要求的配置檔案:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

  <bean id="v" class="com.bjsxt.spring.factory.Train">
  </bean>
  
  <!--  //v=com.bjsxt.spring.factory.Car  -->


</beans>

測試程式:

package com.bjsxt.spring.factory;

import java.io.IOException;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {

	/**
	 * @param args
	 * @throws IOException 
	 */
	public static void main(String[] args) throws Exception {
		BeanFactory f = new ClassPathXmlApplicationContext("applicationContext.xml");
		Object o = f.getBean("v");
		Moveable m = (Moveable)o;
		m.run();
	}

}
配置檔案配置的火車類,列印:
小火車嗚嗚嗚...
換成Car,列印:
冒著煙奔跑中...

下面來模擬Spring的Bean工廠

Spring的BeanFactory ,就是一個容器,是用一個map實現的,就是從配置檔案讀取 <bean id="v" class="com.bjsxt.spring.factory.Train"/> 這樣的配置,遍歷解析這樣的xml配置,以id為key,class後的類全限定名用反射生成的物件為value,放到這個map中去。當用的時候,直接map.get(id); 獲取這個Bean物件。

ClassPathXmlApplicationContext是BeanFactory的一種實現。這裡模擬這種實現。

​ 這裡模擬Spring的Bean工廠:

public interface BeanFactory {

	Object getBean(String id);
}

ClassPathXmlApplicationContext:

public class ClassPathXmlApplicationContext implements BeanFactory{
	//存放一個個Bean物件的容器,
	private Map<String, Object> container = new HashMap<String, Object>();
	
	// 構造方法找到配置檔案,讀取xml配置檔案
	public ClassPathXmlApplicationContext(String fileName) throws Exception{
		SAXBuilder sb = new SAXBuilder();
		Document doc = sb.build(this.getClass().getClassLoader()
				.getResourceAsStream(fileName));
		Element root = doc.getRootElement();
		List list = XPath.selectNodes(root, "/beans/bean");
		System.out.println(list.size());

		for (int i = 0; i < list.size(); i++) {
			Element bean = (Element) list.get(i);
			String id = bean.getAttributeValue("id");
			String clazz = bean.getAttributeValue("class");
			Object o = Class.forName(clazz).newInstance();
			container.put(id, o);
			System.out.println(id + " " + clazz);
		}
	}

	//讀取配置檔案,讀取id為傳進來的id的Bean,例項化
	@Override
	public Object getBean(String id) {
		return container.get(id);
	}

	
}

測試程式:

public static void main(String[] args) throws Exception{
		
		BeanFactory f = new ClassPathXmlApplicationContext("com/lhy/springfactory/applicationContext.xml");
		Object o = f.getBean("v");
		Moveable m = (Moveable)o;
		m.run();
		
		Train trian = (Train)f.getBean("trian");
		trian.run();
		
	}

applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
  <bean id="v" class="com.lhy.springfactory.Car"/>
	<bean id="trian" class="com.lhy.springfactory.Train"/>
  <!--  //v=com.bjsxt.spring.factory.Car  -->
</beans>

列印:

2

v com.lhy.springfactory.Car

trian com.lhy.springfactory.Train

冒著煙奔跑中...

小火車嗚嗚嗚...

這樣,就把類配置在了配置檔案裡,更靈活了!