原始碼中的設計模式--工廠模式

北漂程式設計師發表於2022-05-08

本文要解決的幾個問題,

1、什麼是工廠模式

2、工廠模式分為哪幾種

3、工廠模式的優點

4、原始碼中有哪些地方使用了工廠模式

一、模式入場

  看到標題想必小夥伴很好理解,所謂“工廠模式”從字面上就可以理解,比亞迪工廠的作用不就是生產比亞迪汽車的,在java中的工廠模式就是用來產生例項的。現在我有這樣一個類,

Car.java

package com.my.factory;
/**
 * 汽車類
 * @date 2022/5/8 11:15
 */
public class Car {
    //汽車唯一編碼
    private String code;
    //汽車型號
    private String model;
}

現在要使用Car生成一個具體的例項,那麼平時的做法肯定是new了,如下

Car car=new Car();

  現在有這樣一個場景,在很多地方都要使用Car的例項,那麼每一次使用都new一次,都是重複的程式碼從程式碼規範層面上就不好看,而且這也不符合設計原則。我們是不是可以專門有一個類來生成Car的例項,在需要Car例項的地方只需要呼叫該類的方法即可,為此有了下面的工具類,

package com.my.factory.simple;

import com.my.factory.Car;
/**
 * 簡單Car工廠
 * @date 2022/5/8 11:22
 */
public class SimpleCarFactory {
    public Car productCar(){
        return new Car();
    }
}

現在想生成Car,就不再使用new了,我呼叫工具類就可以了,

SimpleCarFactory carFactory=new SimpleCarFactory();
Car car=carFactory.productCar();

  有了SimpleCarFactory工具類就好多了,呼叫其productCar()方法就給我返回Car例項了,擺脫了new的方式,再也不用被隔壁的小姐姐嘲笑只會new了。

  上面提到的SimpleCarFactory工具類,其實就是工廠模式的一種實現,給它起個名字叫“簡單工廠”,《Head first 設計模式》一書中給出的釋義是

簡單工廠其實不是一個設計模式,反而比較像是一種程式設計習慣。但由於經常被使用,所以我們給它一個“Head First Pattern榮譽獎”。

上面說了簡單工廠更像是一種程式設計習慣,不過這裡我也把它看作是工廠模式的一種實現方式。

  在使用了一段SimpleCarFactory類後,有小夥伴提出每次都需要new一個SimpleCarFactory的例項才能呼叫其productCar()方法,既然是工具類,把productCar()方法宣告為static不是更好,的確在設計理念上又進了一步,

package com.my.factory.simple;

import com.my.factory.Car;
/**
 * 簡單Car工廠
 * @date 2022/5/8 11:22
 */
public class SimpleCarFactory {
    public static Car productCar(){
        return new Car();
    }
}

再使用的時候只需這樣用就好了,

Car car=SimpleCarFactory.productCar();

上面的這種方式給它起個名字叫“簡單靜態工廠”,不知不覺中又會了另一種實現,可以和隔壁的小姐姐去炫耀一番了。

“簡單工廠”和“簡單靜態工廠”都是有一個專門的類來生成例項,區別是後者的方法是靜態的。

二、深入工廠模式

  上面說到的無論是“簡單工廠”還是“簡單靜態工廠”其實本質上都是一樣的,都是在一個類中生成類的例項。

2.1、工廠方法模式

  還是拿上面的汽車工廠的例子來舉例,有這樣的一個場景,由於汽車訂單激增,一個工廠已經無法完成訂單了,必須要新建一個工廠來生產汽車,而且每個工廠可以生產不同型別的汽車,現在要對上面的SimpleCarFactory和Car進行改造。假設有兩個工廠分別是ConcreteCarFactoryOne和ConcreteCarFactoryTwo,生產的汽車有Biyadiar、XiandaiCar等,現在的類圖如下,

ConcreteCarFactoryOne.java

package com.my.factory.concrete;

import com.my.factory.BiyadiCar;
import com.my.factory.ConcreteCar;
/**
 * 生產比亞迪汽車的工廠
 * @date 2022/5/8 16:17
 */
public class ConcreteCarFactoryOne extends ConcreteCarFactory {

    @Override public ConcreteCar productCar() {
        car = new BiyadiCar();
        car.setCode("1");
        car.setModel("byd");
        return car;
    }
}

ConcreteCarFactoryTwo.java

package com.my.factory.concrete;

import com.my.factory.ConcreteCar;
import com.my.factory.XiandaiCar;

/**
 * 生產現代汽車的工廠
 * @date 2022/5/8 16:42
 */
public class ConcreteCarFactoryTwo extends ConcreteCarFactory {
    @Override public ConcreteCar productCar() {
        car = new XiandaiCar();
        car.setCode("2");
        car.setModel("xiandai");
        return car;
    }
}

好了,兩個工廠類已經完成了,分別生成比亞迪汽車和現代汽車,細心的小夥伴發現一個問題,這兩個工廠都有productCar()方法,可不可以抽取出來,答案是必須抽出來,我這裡抽取為抽象類,讓ConcreteCarFactoryOne和ConcreteCarFactoryTwo分別進行實現,

ConcreteCarFactory.java

package com.my.factory.concrete;

import com.my.factory.ConcreteCar;

/**
 * 抽象工廠
 * @date 2022/5/8 16:46
 */
public abstract class ConcreteCarFactory {
    //要生產的汽車,由子類進行初始化
    protected ConcreteCar car;

    //由子類實現該方法
    protected abstract ConcreteCar productCar();

    //給汽車噴漆
    public void sprayPaint() {
        System.out.println("給--" + car + "--噴漆");
    }
}

ConcreteCarFactoryOne和ConcreteCarFactoryTwo的修改不再貼出,聰明的你肯定知道怎麼改。

另外,對於汽車類這裡也抽出了一個公共類,BiyadiCar和XiandaiCai會繼承改類,

ConcreteCar.java

package com.my.factory;

/**
 * 汽車介面
 * @date 2022/5/8 16:21
 */
public class ConcreteCar {
    protected String code;
    protected String model;

    //省略get/set方法
}

BiyadiCar.java

package com.my.factory;

/**
 * 比亞迪汽車
 * @date 2022/5/8 16:18
 */
public class BiyadiCar extends ConcreteCar{
    @Override public String toString() {
        return "BiyadiCar{" + "code='" + code + '\'' + ", model='" + model + '\'' + '}';
    }
}

XiandaiCar這裡就不再給出類似的程式碼。

下面看下測試類,

TestConcreteCarFactory.java

package com.my.factory.concrete;

import com.my.factory.ConcreteCar;

/**
 * 測試類
 * @date 2022/5/8 16:57
 */
public class TestConcreteCarFactory {
    public static void main(String[] args) {
        ConcreteCarFactory biyadaCarFactory = new ConcreteCarFactoryOne();
        ConcreteCar biyadiCar = biyadaCarFactory.productCar();
        biyadaCarFactory.sprayPaint();

        ConcreteCarFactory xiandaiCarFactory = new ConcreteCarFactoryTwo();
        ConcreteCar xiandaiCar = xiandaiCarFactory.productCar();
        xiandaiCarFactory.sprayPaint();
    }
}

測試結果可以看到建立了兩個不同的汽車

給--BiyadiCar{code='1', model='byd'}--噴漆
給--XiandaiCar{code='2', model='xiandai'}--噴漆

其UML圖如下

上面便是工廠模式的工廠方法模式的實現,《Head frist 設計模式》一書中對此模式給出的釋義是

工廠方法模式定義了一個建立物件的介面,但由子類決定要例項化的類是哪一個。工廠方法讓類把例項化推遲到子類。

從上面的UML圖中可以很好的理解上面的話,”一個建立物件的介面“這裡指的不但但是interface,在本例中定義的則是一個抽象方法。同時類的例項化是在具體的子類中實現的,到底要例項化什麼樣的類則要根據相應的工廠來決定。

2.2、抽象工廠模式

  前面我們建立了兩個工廠,都是用來生產汽車的,唯一的區別是生產的汽車是不一樣的。現在有這樣的場景,一個工廠僅生產汽車太浪費資源了,現在新能源是發展的趨勢,每個工廠再上一條生產線生產電池吧,為此,上面的工廠需要提供一個介面來生產汽車和電池,這次我們不使用抽象類了,使用介面,

package com.my.factory.concrete.factory;

import com.my.factory.Battery;
import com.my.factory.ConcreteCar;

/**
 * 生產汽車和電池的介面
 * @date 2022/5/8 18:46
 */
public interface ConcreteFactory {
    //生產汽車
    ConcreteCar productCar();
    //生產電池
    Battery productBattery();
}

上面的介面中有兩個方法一個生產汽車一個生產電池,相應的實現類也必須實現這兩個方法。這種一個介面中包含多個生成例項的模式稱為”抽象工廠模式“,《Head first 設計模式》一書中給出的釋義是,

抽象工廠模式提供一個介面,用於建立相關或依賴物件的家族,而不需要明確指定具體類。

該釋義說的很清楚,注意”家族“二字,說的就是包含一個以上的方法,後續”不需要明確指定具體類“,則是要在具體使用的時候選擇合適的實現即可。

三、追尋原始碼

3.1、mybatis中的SqlSessionFactory

在mybatis中的SqlSessionFactory,便是工廠模式的一種體現,更確切的說是抽象工廠模式,

在SqlSessionFactory中有openSession方法,且該方法有多個過載,並且還有一個getConfiguration方法,下面看起具體的實現類,

共有兩個實現分別是DefaultSqlSessionFactory和SqlSessionManager,看下openSession()方法在DefaultSqlSesssionFactory中的實現

最終返回的是一個SqlSession的實現DefaultSqlSession,和上面的工廠模式的UML神奇的類似。

3.2、Spring中的BeanFactory

在spring中有BeanFactory介面,

可以看到該介面中有getBean、getType、getAliases方法,這些都可以作為抽象工廠的證據,小夥伴們說了還有isSingleton、containsBean方法,這些我們說不能作為工廠模式的證據,因為,工廠模式的定義是要生成例項,也就是說工廠模式要建立並返回一個類的例項,而isSingleton、containsBean沒有建立例項。看下該介面的實現

在其實現中有AbstractBeanFactory實現,其getBean方法的一個實現如下,

  由於spring實現的太複雜,這裡不再詳述。有小夥伴會問沒看到在實現中有new啊,的確沒有,在spring的實現中是通過反射的方式建立的,我們說生成類的例項不僅只有new的方式,工廠模式的關鍵在於生成類的例項,而不在於如何生成。

 

四、總結

  總結下工廠模式的要點,

  1、工廠模式分為簡單工廠、簡單靜態工廠、工廠方法、抽象工廠四種不同的實現;

  2、工廠模式的使用原則在於如何建立類的例項,可以使用new,也可以使用反射、反序列化等方式;

  3、工廠模式擺脫了使用者傳統的new的方式,讓物件的建立集中在一處,對設計進行了解耦,讓使用者不必關心建立物件的細節,只需使用介面;

 

  今天的分享就到這裡了,小夥伴們回想下文章開頭提的幾個問題都有答案了麼,沒有的話多讀幾遍哦。

相關文章