我們舉一個摘水果的例子,果園中種着蘋果樹、香蕉樹、桔子樹等等果樹,當有三個小夥伴想要喫蘋果時,他們就需要各自去拿各自想要喫的水果,我們最原始樸素的寫法如下,誰需要誰就自己去拿就好了:
/**
* 不使用設計模式:最原始的想法,哪裏用就直接在哪裏new出來相應的對象
*/
public class PeterClient {
//Peter自己喫水果
public static void main(String[] args) {
peterdo();
jamesdo();
lisondo();
}
//Peter自己喫水果
public static void peterdo() {
Fruit fruit = new Apple();
fruit.draw();
//。。。直接啃着喫,喫掉了
System.out.println("-----------------");
}
//送給james,切開喫
public static void jamesdo() {
Fruit fruit = new Apple();
fruit.draw();
//。。。切開喫
System.out.println("-----------------");
}
//送給lison榨汁喝
public static void lisondo() {
Fruit fruit = new Orange("peter", 100);
fruit.draw();
//。。。榨汁運作
System.out.println("-----------------");
}
}
上面代碼可以看到,會有很多人到處自己去採摘new所需要的水果,然而我們的果園中的果子都是有一定數量的,此時若沒有沒有一個統一的生產水果的類去進行管理,那麼果園將會混亂不堪。
下面我們就引出解決方案:
簡單工廠模式
簡單工廠模式(又叫作靜態工廠方法模式),其屬於創建型設計模式,但是並不屬於 23種GoF設計模式之一。提到它是爲了讓大家能夠更好地理解後面講到的工廠方法模式。
定義:簡單工廠模式屬於創建型模式,其又被稱爲靜態工廠方法模式,這是由一個工廠對象決定創建出哪一種產品類的實例。
和剛剛大家自己到果園中胡亂採摘果子不同,這裏我們抽象出來了一個果園的工廠類,工廠負責各種果樹的果子的採摘工作。
簡單工廠模式的實現:
我們使用一個單獨的類來管理,專門去果園採摘各種果子,簡單工廠模式的結構圖如圖:
我們創建一個水果工廠類,它提供了一個靜態方法 getFruit 用來生產各種水果。你只需要傳入自己想要的水果種類,它就會實例化相應的水鬼對象,代碼如下所示:
public class StaticFactory {
public static final int TYPE_APPLE = 1;//蘋果
public static final int TYPE_ORANGE = 2;//桔子
public static final int TYPE_BANANA = 3;//香蕉
/**
* 調用方式一===========================================
*/
public static Fruit getFruit(int type) {
if (TYPE_APPLE == type) {
return new Apple();
} else if (TYPE_ORANGE == type) {
return new Orange("Peter", 80);
} else if (TYPE_BANANA == type) {
return new Banana();
}
return null;
}
/**
* 調用方式二===========================================
* 多方法工廠
*/
public static Fruit getFruitApple() {
return new Apple();
}
public static Fruit getFruitOrange() {
return new Orange("Peter", 80);
}
public static Fruit getFruitBanana() {
return new Banana();
}
}
具體使用方式如下:
public class StaticFactoryClient {
public static void main(String[] args) {
peterdo();
jamesdo();
lisondo();
}
//Peter獲取香蕉方式一
public static void peterdo() {
Fruit fruit = StaticFactory.getFruit(StaticFactory.TYPE_BANANA);//調用方式一
fruit.draw();
//。。。直接啃着喫,喫掉了
System.out.println("-----------------");
}
//James獲取香蕉方式二
public static void jamesdo() {
Fruit fruit = StaticFactory.getFruitBanana();//調用方式二
fruit.draw();
//。。。切開喫
System.out.println("-----------------");
}
//lison獲取蘋果
public static void lisondo() {
Fruit fruit = StaticFactory.getFruit(StaticFactory.TYPE_APPLE);
fruit.draw();
//。。。榨汁動作
System.out.println("-----------------");
}
}
上面的例子中,果園工廠負責了所有種類果子的採摘,上面我們只有三種水果:蘋果,桔子,香蕉。但是隨着果園不斷髮展,可能還會有西瓜、草莓、荔枝等等新的種類添加到果園中,也就是我們的靜態工廠方法中會不斷的需要更改,這樣就違反了開閉原則,每次添加新的水果品種,都需要去修改下這個靜態工廠類,添加更多的水果種類,這顯然是不符合軟件設計規則的。
因此,靜態(簡單)工廠設計模式,在比較簡單的業務場景中使用是比較簡潔高效的,但要是業務產生的種類較多,那麼就顯得不合時宜了。
如圖所示,果園又新增了多個種類的水果:
工廠方法模式:
隨着種植的水果品類不斷增加,StaticFactory類不斷修改,方法擴展極爲龐大。水果品種的擴展方式不優雅。爲了適應這類業務的發展,我們引出工廠方法模式,來解決上面的問題:
簡單工廠模式中,我們把所有的生產任務都交由一個工廠去完成,一是任務重,二是每次新加種類都需要重新修改這個工廠。
爲了解決上面的問題,我們想到可以將這一個大雜燴的工廠拆分成多個種類的小工廠,分門別類的小工廠去生產各自負責的任務就好了。
如圖所示,當我們需要再新增水果種類時,再添加一個西瓜、草莓、荔枝的工廠即可。
工廠方法模式定義:定義一個用於創建對象的接口,讓子類決定實例化哪個類。工廠方法使一個類的實例化延遲到其子類。簡單來說就是搞一個工廠類的接口,下面有很多不同的小工廠瓜分了原來靜態工廠的職責,後面具體誰需要用到哪個小工廠自己去new出來這個小工廠的實現即可。
如圖所示,我們定義一個FruitFactory的工廠接口,下面有蘋果工廠、香蕉工廠、桔子工廠這三個實現類,工廠類結構與產品類結構一一對應,每一種產品都對應一個工廠子類。當新增一個產品類型時,新加對應的工廠子類即可,不再需要修改既有類。對應的代碼:
工廠接口:
/**
* 工廠方法接口
*/
public interface FruitFactory {
public Fruit getFruit();//摘水果指令
}
三個對應的水果工廠:
蘋果工廠:
/**
* 蘋果工廠方法模式
*/
public class AppleFactory implements FruitFactory{
public Fruit getFruit(){
return new Apple();
}
}
香蕉工廠:
/**
* 香蕉工廠方法模式
*/
public class BananaFactory implements FruitFactory{
public Fruit getFruit(){
return new Banana();
}
}
桔子工廠:
/**
* 橘子工廠方法模式
*/
public class OrangeFactory implements FruitFactory{
public Fruit getFruit(){
return new Orange("Peter",80);
}
}
工廠方法測試類:
/**
* 工廠方法模式測試
*/
public class FactoryTest {
private static FruitFactory fruitFactory;
public static void main(String[] args) {
//初始化蘋果工廠
fruitFactory = new AppleFactory();//spring配置
peterdo();
jamesdo();
lisondo();
}
//Peter自己喫水果
public static void peterdo() {
Fruit fruit = fruitFactory.getFruit();
fruit.draw();
System.out.println("-----------------");
}
//james喫
public static void jamesdo() {
Fruit fruit = fruitFactory.getFruit();
fruit.draw();
System.out.println("-----------------");
}
//lison喫
public static void lisondo() {
Fruit fruit = fruitFactory.getFruit();
fruit.draw();
System.out.println("-----------------");
}
}
因爲這裏使用的是蘋果工廠,最終打印出來的如下:
總結工廠方法模式:將靜態工廠的任務拆分到了不同的各個工廠中,當需要更多的商品種類時,我們不斷擴充小工廠即可。
我們繼續往下看這個場景,爲了果園的發展,我們想把成熟的新鮮水果包裝起來,產生更多的附加值,爲果園增收,因此開起了水果店,當然這時我們需要給各種果子打上包裝盒。
和上面的水果工廠功能類似,我們有新添加了水果包裝的工廠,如下圖所示,
水果工廠和水果包裝工廠,需要相互配合才能完成產品的生產。就是蘋果工廠生產的蘋果需要和蘋果包裝工廠生產的包裝盒相匹配,最終做出蘋果禮盒。但是這裏有一個問題,就是工人們有可能會把蘋果錯放到了香蕉盒、或者錯放到了桔子盒中,造成顧客買的香蕉禮盒,而裏面都是蘋果的尷尬場面。代碼如下:
/**
* 水果店測試:
* 使用工廠方法測試類,會產生打包錯誤的情況
*/
public class FruitStoreTest {
private static FruitFactory fruitFactory;
private static BagFactory bagFactory;
public static void main(String[] args) {
pack();
}
private static void pack() {
//初始化蘋果工廠
fruitFactory = new AppleFactory();
Fruit fruit = fruitFactory.getFruit();
fruit.draw();
//錯誤獲取到了香蕉的包裝
bagFactory = new BananaBagFactory();
Bag bag = bagFactory.getBag();
//錯誤的把蘋果放到了香蕉的包裝中
bag.pack();
}
}
我們可以看到,蘋果工廠生產的蘋果,由於錯誤獲取到了香蕉的包裝盒,因此將蘋果放到了香蕉禮盒中,最終導致問題的產生,那麼如何避免這類問題的產生呢?
因此爲了避免這種場景的出現,我們很自然會想到,是否可以將生產蘋果工廠和蘋果包裝工廠進行合併,也就是說一個蘋果工廠就完成了蘋果採摘和蘋果包裝的兩份工作呢。
下面我們利用抽象工廠來解決上面的問題:
對應的類圖:
爲了避免蘋果工廠生產的蘋果錯誤放到了其他工廠生產的包裝盒中,我們可以將蘋果工廠和蘋果包裝工廠的任務統一到一個新的蘋果工廠中去。如下改寫:
抽象工廠:
/**
* 抽象水果工廠
*/
public abstract class AbstractFactory {
public abstract Fruit getFruit();
public abstract Bag getBag();
}
新蘋果工廠:
/**
* 新蘋果工廠
*/
public class AppleFactory extends AbstractFactory{
@Override
public Fruit getFruit() {
return new Apple();
}
@Override
public Bag getBag() {
return new AppleBag();
}
}
新香蕉工廠:
/**
* 新香蕉工廠
*/
public class BananaFactory extends AbstractFactory{
@Override
public Fruit getFruit() {
return new Banana();
}
@Override
public Bag getBag() {
return new BananaBag();
}
}
新桔子工廠:
/**
* 新桔子工廠
*/
public class OrangeFactory extends AbstractFactory{
@Override
public Fruit getFruit() {
return new Orange("Peter",50);
}
@Override
public Bag getBag() {
return new OrangeBag();
}
}
抽象工廠測試類:
/**
* 抽象工廠模式測試
* 按訂單發送貨品給客戶
*/
public class OrderSendClient {
public static void main(String[] args) {
sendFruit();
}
public static void sendFruit() {
//初始化工廠
AbstractFactory factory = new AppleFactory();//spring使用注入方式
//得到水果
Fruit fruit = factory.getFruit();
fruit.draw();
//得到包裝
Bag bag = factory.getBag();
bag.pack();
//以下物流運輸業務。。。。
}
}
測試結果:
總結,抽象工廠就是把原來有配合的工廠方法的工廠組合起來,以防止配合上的出錯。