工廠模式-理解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
冒著煙奔跑中...
小火車嗚嗚嗚...
這樣,就把類配置在了配置檔案裡,更靈活了!