前言
只有光頭才能變強
回顧前面:
昨天寫了單例模式了,今天是時候寫工廠模式啦~
工廠模式我個人認為其實比較難理解的,如果有接觸過|聽過|見過該模式的同學很可能就會想:我自己new
一個對象出來就好了,簡單快捷。用得着你這個工廠模式嗎?搞一個工廠出來還要寫一大堆的代碼呢~
網上的很多資料都是在闡述着:工廠模式的好處就是解耦。相信大家對解耦這個詞也不陌生,那解耦究竟有什么好處呢?
本文章試圖去解釋為什么要用工廠模式,用了工廠模式的好處是什么,以及工廠模式衍生出的三種形式究竟有什么區別~~
那么接下來就開始吧,如果有錯的地方希望能多多包涵,並不吝在評論區指正!
一、工廠模式概述
在《設計模式之禪》這本書中分了兩章節講工廠模式:
- 工廠方法模式
- (ps:其中里面講到了簡單工廠模式)
- 抽象工廠模式
網上的大部分資料都是將工廠模式分成三種:
- 簡單/靜態工廠模式
- 工廠方法模式
- 抽象工廠模式
看完上面的敘述是不是想打死我,什么鳥玩意?不急哈,下面我會一一講到~~
1.1為什么要用工廠模式?
想想我們為什么要用工廠模式?下面我就簡單舉例子:
文件IO的操作我們會經常用得到吧,所以BufferedReader對象經常要創建的:
// 創建一個BufferedReader對象
BufferedReader bf = new BufferedReader(new FileReader(new File("aa.txt")));
你說麻煩嗎?其實也不麻煩,就一行代碼嘛,哪里麻煩了~如果不太熟悉IO流的同學就沒有那么機靈了,創建一個BufferedReader可能就是以下的代碼了:
File file = new File("aa.txt");
FileReader fileReader = new FileReader(file);
BufferedReader bufferedReader = new BufferedReader(fileReader);
你說麻煩嗎?其實也不麻煩,不就是三行代碼嘛,哪里麻煩了~如果這個應用很多的類上都用到了BufferedReader對象的話,那每個類都寫上這三行代碼了。那你說麻煩嗎?那肯定麻煩啊,還用想啊....
可以看出來,創建一個BufferReader對象里面需要一個FileReader對象,而FileReader對象又要File對象。那創建這個BufferReader對象還是比較麻煩的(代碼上看不麻煩,從構造上看還是挺麻煩的)!
雖然比較麻煩,但我們還能用,能用就行!於是乎,我們就去寫代碼了,現在有三個類都要進行文件的讀寫操作,於是他們就有這樣的代碼:
public class FileOperateA {
public static void main(String[] args) throws FileNotFoundException {
File file = new File("aa.txt");
FileReader fileReader = new FileReader(file);
BufferedReader bufferedReader = new BufferedReader(fileReader);
// 讀寫文件....
}
}
此時:上頭說,我要換成LineNumberReader來讀寫,有這個需求!那我們作為一個寫代碼的,能怎么辦?很絕望也需要去完成呀。
- 不熟悉IDE的小伙子就一個一個將BufferedReader改成LineNumberReader,現在就3個類用到了BufferedReader,也就改6次而已。(ps:那如果很多地方都用到了呢?)
- 熟悉IDE的小伙子就全局替換重構,妥妥的!
哎,寫個代碼屁事真多...那有沒有一種方法能夠讓創建對象變得簡單而且修改對象時能很方便呢?
- 哎,工廠模式就行了。
再說從面向對象的角度來看:我一個操作文件的類還要我會創建BufferReader是不是有點過分了?(職責沒有分工好)
- 交給工廠來創建對象這就很面向對象了!
1.2體驗工廠模式
何為工廠?將我們的產品都交由工廠來生產!我現在用的iphone5s,從哪來?從富士康組裝而來,富士康是工廠。我用得着知道iphone5s在富士康是怎么組裝起來的嗎?不需要。
來,我們來改造一下上面的例子。首先我們創建一個工廠類,它可以生產Reader對象!
// 創建Reader對象的工廠
public class ReaderFactory {
public static Reader getReader() throws FileNotFoundException {
File file = new File("aa.txt");
FileReader fileReader = new FileReader(file);
BufferedReader reader = new BufferedReader(fileReader);
return reader;
}
}
那么我們要得到BufferReader對象就賊簡單了:
public class FileOperateA {
public static void main(String[] args) throws FileNotFoundException {
//-------我有工廠了,還用自己搞嗎?不用了!
//File file = new File("aa.txt");
//FileReader fileReader = new FileReader(file);
//BufferedReader bufferedReader = new BufferedReader(fileReader);
//-------我有工廠了,還用自己搞嗎?不用了!
// 用工廠來創建出對象
Reader reader = ReaderFactory.getReader();
// 讀寫文件....
}
}
工廠將我們創建的對象過程給屏蔽了!
此時我要改成LineNumberReader怎么玩?在工廠上改一下就好了:
我們的調用方FileOperateA|FileOperateB|FileOperateC
這些類完全就不用變!
1.3使用工廠方法的好處
從上面的工廠模式體驗我們就可以看到:
- 我們修改了具體的實現類,對客戶端(調用方)而言是完全不用修改的。
- 如果我們使用
new
的方式來創建對象的話,那么我們就說:new
出來的這個對象和當前客戶端(調用方)耦合了!- 也就是,當前客戶端(調用方)依賴着這個
new
出來的對象!
- 也就是,當前客戶端(調用方)依賴着這個
這就是解耦的好處!
我再放下我之前練習的時候寫過的代碼吧:
我有一個DaoFactory,邏輯很簡單就是專門創建Dao對象的~
那么在Service層就可以使用工廠將想要的Dao對象初始化了~
此時我們的Service與Dao的對象低耦合的~
- 大家可能看不出有什么好處,還弄了一大堆的字符串啥的~~
在Service與Controller層我也弄了一個ServiceFactory,根據當時業務的需要(添加權限),我創建Service時就非常靈活了:
二、如何使用工廠模式
在一開始我就說了,工廠模式可以分成三類:
- 簡單/靜態工廠模式
- 工廠方法模式
- 抽象工廠模式
下面我就逐一來介紹一下每一種工廠模式有什么不一樣~
三種模式都以:Java3y要買寵物的例子來講解~
2.1工廠方法模式
很多博客都是以簡單/靜態工廠模式,工廠方法模式,抽象工廠模式這個順序來講解工廠模式的。我認為按書上的順序比較好理解~因為簡單/靜態工廠模式是在工廠方法模式上縮減,抽象工廠模式是在工廠方法模式上再增強。
- 所以我就先講工廠方法模式了。
Java3y每天寫代碼很無聊,想要買只寵物來陪陪自己。於是乎就去寵物店看寵物啦~~~
作為一間寵物店,號稱什么寵物都有!於是乎,店主宣傳的時候就說:我的寵物店什么寵物都有!
於是構建寵物的工廠就誕生了~
// 號稱什么寵物都有
public interface AnimalFactory {
// 可以獲取任何的寵物
Animal createAnimal();
}
當然了,主流的寵物得進貨一些先放在店里充充門面,一些特殊的寵物就告訴顧客要時間進貨~
- 所以,我們就有了構建貓和狗的工廠(繼承着所有寵物的工廠)
貓工廠:
// 繼承着寵物工廠
public class CatFactory implements AnimalFactory {
@Override
// 創建貓
public Animal createAnimal() {
return new Cat();
}
}
狗工廠也是一樣的:
// 繼承着寵物工廠
public class DogFactory implements AnimalFactory {
// 創建狗
@Override
public Animal createAnimal() {
return new Dog();
}
}
嗯,還有我們的實體類:貓、狗、動物(多態:貓和狗都是動物,可以直接用動物來表示了)
動物實體類:
public abstract class Animal {
// 所有的動物都會吃東西
public abstract void eat();
}
貓實體類:
public class Cat extends Animal {
// 貓喜歡吃魚
@Override
public void eat() {
System.out.println("貓吃魚");
}
}
狗實體類:
public class Dog extends Animal {
// 狗喜歡吃肉
@Override
public void eat() {
System.out.println("狗吃肉");
}
}
那么現在Java3y想要一只狗,跟了寵物店老板說,寵物店老板就去找狗回來了:
// 去找狗工廠拿一只狗過來
AnimalFactory f = new DogFactory();
// 店主就拿到了一只狗給Java3y
Animal a = f.createAnimal();
a.eat();
System.out.println("關注公眾號:Java3y");
那么現在Java3y想要一只貓,跟了寵物店老板說,寵物店老板就去找貓回來了:
// 去找貓工廠拿一只貓過來
AnimalFactory ff = new CatFactory();
// 店主就拿到了一只貓給Java3y
Animal aa = ff.createAnimal();
aa.eat();
System.out.println("關注公眾號:Java3y");
如果這個時候Java3y說想要一只蜥蜴怎么辦啊?沒問題啊,店主搞個蜥蜴工廠就好了~~
// 要買蜥蜴..
AnimalFactory fff = new LizardFactory();
Animal aaa = ff.createAnimal();
aaa.eat();
優點:
- 1:客戶端不需要在負責對象的創建,明確了各個類的職責
- 2:如果有新的對象增加,只需要增加一個具體的類和具體的工廠類即可
- 3:不會影響已有的代碼,后期維護容易,增強系統的擴展性
缺點:
- 1:需要額外的編寫代碼,增加了工作量
工廠方法類圖:
2.2簡單/靜態工廠模式
現在寵物店生意不好做啊,號稱“什么寵物都有",這吹過頭了~~於是店主只賣兩種常見的寵物了。
- 既然就只有兩種寵物的話,那就沒必要有”貓廠“、”狗廠“了,一個貓狗廠就行了!
所以我們的工廠是這樣子的:
public class AnimalFactory {
public static Dog createDog() {
return new Dog();
}
public static Cat createCat() {
return new Cat();
}
// 外界想要貓要狗,這里創建就好了
public static Animal createAnimal(String type) {
if ("dog".equals(type)) {
return new Dog();
} else if ("cat".equals(type)) {
return new Cat();
} else {
return null;
}
}
}
三個實體還是沒變(動物、貓、狗)....
那么Java3y去寵物店買貓狗的時候,告訴老板我要貓、我要狗:
// 拿到狗
Animal A = AnimalFactory.createAnimal("dog");
A.eat();
// 拿到貓
Animal C = AnimalFactory.createAnimal("cat");
C.eat();
現在問題來了:
- 1:我想要一個豬,可是我的工廠類沒有豬
- 2:我就去改代碼,寫可以創建豬對象的
- 3:接着,我又要其他的動物
- 4:我還是得改代碼
- 5...................
- 6:這就是簡單工廠類的缺點:當需求改變了,我就要改代碼.
簡單工廠類的優點也很明顯:我就一個具體的工廠來創建對象,代碼量少。
2.3抽象工廠模式
抽象工廠模式就比較復雜了,我們一般的應用都寫不到。我首先來簡述一下需求吧:
- 現在非常流行在貓狗屆也吹起了一股“性別風”
- 有的喜歡公的
- 有的喜歡母的
那我們的貓和狗都是有性別的,不是公的就是母的~~
- 我們之前在工廠方法模式下是每個動物都開一個工廠,如果動物過多的話,那么就有很多的工廠~
- 那現在我們可以抽取出來:每個動物不是公的就是母的~
- 所以我們有兩個工廠就足夠了!
具體的代碼是這樣的:
我們的最大工廠還是定義了創建什么動物
public interface AnimalFactory {
Animal createDog();
Animal createCat();
}
創建母貓和母狗的工廠:
public class FemaleAnimalFactory implements AnimalFactory {
// 生產母狗和母貓
@Override
public Animal createDog() {
return new FemaleDog();
}
@Override
public Animal createCat() {
return new FemaleCat();
}
}
創建公貓和公狗的工廠:
public class MaleAnimalFactory implements AnimalFactory {
// 生產公狗和公貓
@Override
public Animal createDog() {
return new MaleDog();
}
@Override
public Animal createCat() {
return new MaleCat();
}
}
這是所有動物都擁有的普遍行為:
public abstract class Animal {
// 所有的動物都會吃東西
public abstract void eat();
// 所有的動物都有性別
public abstract void gender();
}
這是貓都擁有的普遍行為:
public abstract class Cat extends Animal {
// 貓喜歡吃魚
@Override
public void eat() {
System.out.println("貓吃魚");
}
}
這是狗都擁有的普遍行為:
public abstract class Dog extends Animal {
// 狗喜歡吃肉
@Override
public void eat() {
System.out.println("狗吃肉");
}
}
貓分為公貓、母貓。狗分為公狗和母狗:
public class FemaleCat extends Cat {
public void gender() {
System.out.println("I am a female Cat");
}
}
.....
簡單來說:工廠方法模式的工廠是創建出一種產品,而抽象工廠是創建出一類產品。
- 一類的產品我們稱之為產品族。
- 貓是一類的,狗也是一類的。所以AnimalFactory定義了兩類產品--->
Animal createDog();
和Animal createCat();
- 貓是一類的,狗也是一類的。所以AnimalFactory定義了兩類產品--->
- 產品的繼承結構稱之為產品等級。
- 所有的動物都是會吃東西的,它們都是有性別的,這是最普遍的。所以Animal定義了兩個抽象方法:
public abstract void eat();
和public abstract void gender();
- 所有的狗都是會吃肉的,所以Dog實現了
eat()
方法- 狗又分成了公狗和母狗,所以定義了兩個類FemaleDog和MaleDog繼承了Dog,實現了
gender()
方法
- 狗又分成了公狗和母狗,所以定義了兩個類FemaleDog和MaleDog繼承了Dog,實現了
- 所有的貓都是會吃魚的,所以Cat實現了
eat()
方法- 貓又分成了公貓和母貓,所以定義了兩個類FemaleCat和MaleCat繼承了Cat,實現了
gender()
方法
- 貓又分成了公貓和母貓,所以定義了兩個類FemaleCat和MaleCat繼承了Cat,實現了
- 所有的動物都是會吃東西的,它們都是有性別的,這是最普遍的。所以Animal定義了兩個抽象方法:
- 具體的工廠是面向多個產品等級結構進行生產。
- 所以FemaleAnimalFactory定義了
createDog()
和createCat()
生產母狗和母貓 - 所以MaleAnimalFactory定義了
createDog()
和createCat()
生產公狗和共貓
- 所以FemaleAnimalFactory定義了
- 找到母工廠就可以創建母貓和母狗,找到公工廠就可以創建公貓和公狗
public static void main(String[] args) {
// 需要性別為母的就去找母工廠
AnimalFactory af = new FemaleAnimalFactory();
// 需要一只母貓
af.createCat().gender();
// 需要一只母狗
af.createDog().gender();
System.out.println("-------------關注公眾號:Java3y-------------------------");
// 需要性別為公的就去找公工廠
AnimalFactory aff = new MaleAnimalFactory();
// 需要一只公狗
aff.createDog().gender();
// 需要一只公貓
aff.createCat().gender();
}
效果:
這是抽象工廠模式的類圖:
抽象工廠模式說到底就是多了一層抽象,減少了工廠的數量。
抽象工廠缺點也很明顯:
- 難以擴展產品族--->如果我再要寵物豬的話
- 那我要修改AnimalFactory、FemaleAnimalFactory、MaleAnimalFactory這些類了~
三、總結
總的來說我們用簡單工廠模式比較多,工廠方式模式的話代碼量會比較大,抽象工廠模式的話需要業務比較大的情況下才會用到(如果有更好的理解方式不妨在評論區留言,一起交流交流漲漲見識~~)
- 工廠模式配合反射來使用也是極好的~
參考資料:
- 《設計模式之禪》
- https://wangjingxin.top/2016/10/27/abstract/--【原創】設計模式系列(九)——抽象工廠模式
- https://www.zhihu.com/question/24843188?sort=created--工廠設計模式有什么用?
- https://blog.csdn.net/lemon_tree12138/article/details/46225213--Java設計模式——工廠模式
- https://www.cnblogs.com/toutou/p/4899388.html--詳解設計模式之工廠模式(簡單工廠+工廠方法+抽象工廠)
- http://www.cnblogs.com/poissonnotes/archive/2010/12/01/1893871.html--工廠模式
如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同學,可以關注微信公眾號:Java3y。為了大家方便,剛新建了一下qq群:742919422,大家也可以去交流交流。謝謝支持了!希望能多介紹給其他有需要的朋友
文章的目錄導航: