本文要解決的幾個問題,
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的方式,讓物件的建立集中在一處,對設計進行了解耦,讓使用者不必關心建立物件的細節,只需使用介面;
今天的分享就到這裡了,小夥伴們回想下文章開頭提的幾個問題都有答案了麼,沒有的話多讀幾遍哦。