正文
編者按:
定義一個用于創(chuàng)建對象的接口,讓子類決定實例化哪一個類。工廠方法使得一個類的實例化延遲到子類。
工廠模式,是迄今為止,使用最多,最廣泛的設計模式之一,它的身影幾乎出現(xiàn)在每一個框架和個人代碼之中。
它是基石,只有充分了解并掌握了工廠模式,才能繼續(xù)的向更深層的設計模式進發(fā)和努力。
今天的可能有點兒繞,如果有不懂的,可以在文章下邊留言,如果有三個以上評論要我講課的話,我就在直播課堂,給大家講解一下。
同時為了更好的講解,我會稍微穿插一個小故事,讓大家能夠很好的掌握工廠模式的三大步。
一、什么是工廠模式?
在上一篇文章中,我們說到了《單例模式》,了解到了它的場景,也學會了它的目的,從模式類型上,我們可以知道,他是一個創(chuàng)建型的設計模式,說白了就是創(chuàng)建一個對象的實例,只不過是單例的 —— 單一實例的。
那今天我們繼續(xù)說下一個創(chuàng)建型的設計模式 —— 工廠模式,工廠模式和單例模式,正好相反,他不是創(chuàng)建單一的,而是創(chuàng)建無窮的,隨著項目而不斷變化的實例場景。
【工廠模式】,英文名稱:Factory Pattern,是開發(fā)中最常用的設計模式之一。這種類型的設計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式。在工廠模式中,****我們在創(chuàng)建對象時不會對客戶端暴露創(chuàng)建邏輯,并且是通過使用一個共同的接口來指向新創(chuàng)建的對象。
從日常使用分類上,工廠模式有三種:
1、簡單工廠模式
2、工廠方法模式
3、抽象工廠模式
不過整體上,思路是類似的,可以統(tǒng)稱為工廠模式,但是一般有人不認為簡單工廠屬于工廠模式,而且同時又把抽象工廠,給單獨提取出來,作為一個新的設計模式,這個無可厚非,今天我們就通過一個生活中的小栗子,然后再配合具體代碼,一步一步學習工廠模式,從簡單工廠,到工廠方法,最后到抽象工廠。
二、簡單工廠模式
生活小場景第一集:
老張呢,平時都是一個人住,為了健康生活,經(jīng)常會自己買菜,洗衣,做飯,周末時不時的就像來點兒花樣(其實并沒有??),比如上周末想吃【椒鹽紫蘇蝦】,是不是聽著名字就想吃,嗯,就是不好做,辛辛苦苦搞了半天,最后還是不太滿意。然后呢,這周末又想吃【紅燒話梅排骨】挑戰(zhàn)一下?結(jié)果到了周末,還是停留在想想的階段,想吃但是不想做!
這個時候,就特別想要一個我的廚師,我吃啥,就給我做啥,就是這么美好,只要我傳遞一個菜名參數(shù),就會給我實例化一個菜,是真正的菜的對象示例,不是一個臆想,那作為代碼的上帝,我們來創(chuàng)建一個吧,一個簡單的個人廚師——簡單工廠。
1、先從最簡單的開始
上邊的例子大家肯定都能看懂,我就不多說了,代碼呢,本來想用這個小故事里的,只不過和我們平時開發(fā)不是很貼近,我就用平時都在用的 Repository 倉儲模式來說明下,而且我也打算下一步寫一個能兼容所有 ORM 的 Repository Lib 組件庫。
首先呢,我們平時開發(fā)的時候,如果想創(chuàng)建一個倉儲方法,來調(diào)取數(shù)據(jù)庫,很簡單,直接上代碼,每個人都能看懂:
[
](javascript:void(0); "復制代碼")
<pre> /// <summary>
/// 定義倉儲類 /// </summary>
public class Repository
{ /// <summary>
/// 獲取數(shù)據(jù)方法 /// </summary>
public void GetData()
{ // 可以進行各種操作,無論是EFCore的 DbSet , // 還是Sqlsugar的 sugarClient // ...
Console.WriteLine("獲取全部數(shù)據(jù)!");
}
} // 調(diào)用
[HttpGet] public object Get()
{ // 實例化倉儲,然后調(diào)用
Repository repository = new Repository();
repository.GetData(); return "";
}</pre>

](javascript:void(0); "復制代碼")
最后結(jié)果出來了:

是不是很簡單?!沒有任何含量,至于其他的什么上下文,咱們不管,只說調(diào)用情況,中間不論業(yè)務邏輯多復雜,咱們平時就是這么寫的,也很符合我們平時開發(fā)的邏輯。打完收工,吃蛋糕!
可能你會說,工廠呢?設計模式呢?沒有說呀,那我要提一個需求了,我們的項目需要用到多種 ORM 共存的倉儲,嗯,你怎么辦?你這個時候可能會說,簡單!看我的:
[
](javascript:void(0); "復制代碼")
<pre> /// <summary>
/// 定義一個 Sqlsugar 倉儲 /// </summary>
public class RepositorySqlsugar
{ public void GetData()
{
Console.WriteLine("獲取 Sqlsugar 全部數(shù)據(jù)!");
}
} /// <summary>
/// 定義一個 EFCore 倉儲 /// </summary>
public class RepositoryEFCore
{ public void GetData()
{
Console.WriteLine("獲取 EFCore 全部數(shù)據(jù)!");
}
} // 然后我們調(diào)取
[HttpGet] public object Get()
{ // 實例化倉儲,然后調(diào)用
RepositorySqlsugar repositorySqlsugar = new RepositorySqlsugar();
repositorySqlsugar.GetData();
RepositoryEFCore repositoryEFCore = new RepositoryEFCore();
repositoryEFCore.GetData(); return "";
}</pre>

](javascript:void(0); "復制代碼")
上邊的代碼也很簡單,相信也有部分小伙伴用過,既然是兩個ORM,那就定義兩個操作類,互相沒影響,還可以自定義擴展,然后分別去調(diào)用,沒啥難度,這個時候,我們可以看到了效果:

2、 設計模式中,要學會封裝
上邊的代碼,我們可以解決多 ORM 共存的問題,不管設計的多粗糙,反正最后我們用到了 EFCore 和 SqlSugar 兩個 ORM,你還可以自定義去調(diào)用,但是我們平時開發(fā)的時候知道,面向?qū)ο笕筇匦浴庋b、繼承、多態(tài)。要學會去封裝,不能隨意的寫很多的類,畢竟內(nèi)容基本是一樣的,好!那既然要封裝,我們就抽象出來一個公共的 Repository,然后繼承它就行了:
[
](javascript:void(0); "復制代碼")
<pre> /// <summary>
/// 定義倉儲抽象類 /// </summary>
public abstract class Repository
{ /// <summary>
/// 抽象方法,獲取數(shù)據(jù) /// </summary>
public abstract void GetData();
} /// <summary>
/// 定義一個 EFCore 倉儲 /// 繼承倉儲父類 /// </summary>
public class RepositoryEFCore : Repository
{ /// <summary>
/// 實現(xiàn)父類抽象方法 /// </summary>
public override void GetData()
{
Console.WriteLine("獲取 EFCore 全部數(shù)據(jù)!");
}
} /// <summary>
/// 定義一個 Sqlsugar 倉儲 /// </summary>
public class RepositorySqlsugar : Repository
{ public override void GetData()
{
Console.WriteLine("獲取 Sqlsugar 全部數(shù)據(jù)!");
}
}</pre>

](javascript:void(0); "復制代碼")
這個時候相信大家都能明白了,我們用兩個類,去繼承抽象倉儲父類,使用的時候,只需要根據(jù)需要,實例不同的倉儲子類就行了,這樣就可以任意的使用:
[
](javascript:void(0); "復制代碼")
<pre> /// <summary>
/// 根據(jù)不同的類型,來實例化不同的對象 /// </summary>
/// <param name="type"></param>
/// <returns></returns>
public Repository GetRepository(string type)
{
Repository repository = null; if (type.Equals("sugar"))
{
repository = new RepositorySqlsugar();
} else if (type.Equals("efcore"))
{
repository = new RepositoryEFCore();
} return repository;
} // 然后去調(diào)用
[HttpGet] public object Get()
{ // 實例化倉儲,然后調(diào)用
Repository sugar = GetRepository("sugar");
sugar.GetData();
Repository efcore = GetRepository("efcore");
efcore.GetData(); return "";
}</pre>
[
](javascript:void(0); "復制代碼")
然后我們直接來看看效果:

依然可以!怎么樣!是不是有那么點兒意思了,是不是有設計感了,概括來說,我們通過一個方法,來控制我們的不同對象的實例輸出,從而實現(xiàn)封裝、繼承、多態(tài)!是不是很像我們的個人管家,只要傳遞過去一個參數(shù),就可以給我們生成多個對象實例。
到了這里,我們可以長舒一口氣,工廠模式這么簡單的么?!不,還遠遠沒有到,我再來問一個問題,項目我的 api 層雖然封裝了這個公共倉儲方法,來控制輸出,但是我們也同時依賴了這些子類倉儲,你想想是不是,這個時候我們就稍微簡單的修改一下就好 —— 為了降低對象之間的耦合。
3、降低對象之間的耦合
那這個時候我們應該怎么辦呢,很簡單,建一個靜態(tài)方法就行了,這樣就提取出去了:
[
](javascript:void(0); "復制代碼")
<pre>/// <summary>
/// 定義簡單倉儲工廠 /// </summary>
public class RepositorySimpleFactory
{ /// <summary>
/// 定義靜態(tài)方法,控制實例對象輸出 /// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static Repository GetRepository(string type)
{
Repository repository = null; if (type.Equals("sugar"))
{
repository = new RepositorySqlsugar();
} else if (type.Equals("efcore"))
{
repository = new RepositoryEFCore();
} return repository;
}
}
[HttpGet] public object Get()
{ // 靜態(tài)方法獲取不同實例,對象之間降低耦合
Repository sugar = RepositorySimpleFactory.GetRepository("sugar");
sugar.GetData();
Repository efcore = RepositorySimpleFactory.GetRepository("efcore");
efcore.GetData(); return "";
}</pre>
[
](javascript:void(0); "復制代碼")
結(jié)果不用看了,依然是有效的,沒錯,你已經(jīng)不知不覺中學會了簡單工廠模式!這次真的很簡單了,從上邊我們看出來了,我們一步一步的往下走,相信每個人都能看懂他的意義,這個時候,我們可以驕傲的說,我們學會了簡單工廠!
但是我們的目的,是工廠方法,簡單工廠只是一個開胃菜,這個時候,我們稍微緩一緩,如果上邊的你都看懂了,可以思考一個問題,如果沒看懂,請繼續(xù)刷第二遍,
那問題就是:
簡單工廠,我們是通過一個變量 type 來進行區(qū)分的,來創(chuàng)建不同的子類對象實例的,這樣不好,因為如果我們以后要增加 dapper 的話,我們還是需要修改這個簡單工廠里的方法,如果很多的話,不僅麻煩,也不符合我們的六大設計模式原則中的其中一個原則,就是OCP原則,中文是【開放關(guān)閉原則】,對增加代碼開發(fā),對修改代碼關(guān)閉。
六大原則:
【單一職責原則】,【里氏替換原則】,【依賴倒置原則】,【接口隔離原則】、【迪米特法則】和【開閉原則】
那我們應該如何做,才能滿足我們的對修改關(guān)閉,對添加開放的原則呢?
沒錯,就是今天的第一個重頭戲 —— 工廠方法模式!
三、工廠方法模式
生活場景第二集:
老張吃中餐吃的越來越多了,有點兒膩了,那咋辦,換個口味唄,打算吃西餐了,比如【魚子醬】,比如【奶油蛋糕】,比如【法式焗蝸?!浚犞€不錯,直接安排!
我不是有一個私人廚師嘛,交給他,反正只要我們傳遞一個菜名,就能創(chuàng)建實例,多好。
然后過了一段時間,廚師離職了。??
大家肯定也能猜得出來,第一是自己忙不過來,第二就是以后我們創(chuàng)建任何一個新的對象實例的時候,都需要修改內(nèi)部方法,讓這個簡單工廠越來越大,越來越笨重,違背了我們的OCP原則。
后來我想通了,既然是吃貨,就直接再招一個西點師傅吧,負責西餐,中餐師傅做中餐,各司其職,然后再來一個個人助理,統(tǒng)一協(xié)調(diào)。
只要我想吃一道菜,私人助理就會自動給我創(chuàng)建指定的廚師,這就是工廠方法模式。
甚至以后我想再吃一個風格的菜,比如面點,直接讓個人助理招一個面點師就行,實現(xiàn)了對內(nèi)修改關(guān)閉,對外新增開放的原則。
剛剛我們文章的開頭,我們說到了,我們可以通過定義多個類,來進行不同 ORM 的劃分,然后我們也封裝了一個GetRepository 的方法,實現(xiàn)了【哪里有變化,哪里就封裝】的設計思路,那我們可以借著這個思路,我們可以建立多個工廠來實現(xiàn)。
說白了就是,我們抽象出來一個工廠,這個工廠用來生產(chǎn)不同的 Repository 對象實例,從而實現(xiàn)目的,那我們就直接開始動手:
[
](javascript:void(0); "復制代碼")
<pre> /// <summary>
/// 1、抽象工廠類 /// </summary>
public abstract class RepositoryFactory
{ /// <summary>
/// 抽象方法,用來返回倉儲對象 /// </summary>
/// <returns></returns>
public abstract Repository CreateRepository();
} /// <summary>
/// 2.1、EFCore 的倉儲工廠 /// 繼承抽象倉儲工廠 /// </summary>
public class RepositoryFactory_EFCore : RepositoryFactory
{ /// <summary>
/// 重寫,生成EFCore 倉儲的實例 /// </summary>
/// <returns></returns>
public override Repository CreateRepository()
{ return new RepositoryEFCore();
}
} /// <summary>
/// 2.2、SqlSugar 的倉儲工廠 /// 繼承抽象倉儲工廠 /// </summary>
public class RepositoryFactory_SqlSugar : RepositoryFactory
{ /// <summary>
/// 重寫,生成 SqlSugar 倉儲的實例 /// </summary>
/// <returns></returns>
public override Repository CreateRepository()
{ return new RepositorySqlsugar();
}
}</pre>

](javascript:void(0); "復制代碼")
這里我們可以看到,我們是在倉儲的基礎上,抽象了一套工廠模式,從之前我們通過 type 類型來判斷,生成倉儲實例,變成了,通過不同的倉儲工廠來生成對應倉儲對象實例。
然后我們來調(diào)用一下:
[
](javascript:void(0); "復制代碼")
<pre> [HttpGet] public object Get()
{ // 初始化創(chuàng)建Repository的兩個倉儲工廠
RepositoryFactory efcoreRepositoryFactory = new RepositoryFactory_EFCore();
RepositoryFactory sugarRepositoryFactory = new RepositoryFactory_SqlSugar(); // 生產(chǎn)efcore倉儲的實例
var efcoreRepository = efcoreRepositoryFactory.CreateRepository();
efcoreRepository.GetData(); //生產(chǎn)sugar倉儲的實體
var sugarRepository = sugarRepositoryFactory.CreateRepository();
sugarRepository.GetData(); return "";
}</pre>

](javascript:void(0); "復制代碼")
結(jié)果依然正確:

好啦!這次可以真正的歇歇了,工廠方法模式,已經(jīng)正式完成了,可能越往后越復雜,不過自己簡單想一想,還是能夠明白的。
你可能會說,說了這么多,這個和文章開頭,我們定義兩個倉儲類,直接輸出不一樣么,現(xiàn)在搞的這么復雜:

真的有必要么?當然!這個還是一個小的項目,如果你看看我的 Blog.Core 項目就知道了,那么小的還有十多個倉儲,中型項目幾十個上百個,然后呢,如果我們要同時使用四種 ORM:EFCore、SqlSugar、Dapper、Ado 等等等等,還有事務操作,所以工廠方法模式,還是很有必要的,除了簡單代碼量,而且更符合我們開發(fā)設計思想:封裝,繼承,多態(tài),OCP原則等等。
上邊我們建立好了工廠方法,如果我們現(xiàn)在需要創(chuàng)建一個 Dapper 的ORM,我們什么都不需要修改,只需要添加幾個類即可:
1、定義一個 RepositoryDapper 倉儲類;
2、定義一個 RepositoryFactory_Dapper 倉儲工廠類;
為了驗證大家的學習是否成功,代碼我就不寫了,自己可以練習喲,有問題,歡迎留言。
大家學到了這里,我建議自己可以練習練習,可以寫一下部分代碼,同時呢,也很好的區(qū)分下【簡單工廠】和【工廠方法】之間的區(qū)別和練習,如果說你已經(jīng)看懂了,或者說自己已經(jīng)練習好了,那咱們就繼續(xù)往下說今天的第二個重要知識點 —— 抽象工廠;
再說抽象工廠之前呢,咱們先簡單總結(jié)一下:
1、我們知道,工廠模式屬于創(chuàng)建型開發(fā)模式的一元,他的作用就是創(chuàng)建我們需要的對象,如果一個一個創(chuàng)建的話,會很麻煩,所以我們誕生出來了一個【簡單工廠】,這個簡單工廠只是簡單的人為的把幾個對象的實例給堆起來,通過type 來區(qū)分,然后分別 new 實例化,有時候也是一個很好的方案,但是這樣有一個弊端,違背了我們開發(fā)六大原則中的——OCP開放關(guān)閉原則,所以這個時候,我們就又多出來一個新的概念【工廠方法】。
2、【工廠方法】是在【簡單工廠】的基礎上,做了相應的改良——通過多個子類的形式,來替換之前的 type 分類法,對內(nèi)修改關(guān)閉,對外新增打開,這樣無論是代碼整潔上,還是擴展封裝上,都有了很好的體驗,同時也滿足了我們的OCP開發(fā)原則。
3、但是!這種方案真的就很好了么,我們再來會議一下,我們無論是簡單工廠,還是工廠方法,都是生成的單獨的一個類,好處是可以一一的慢慢擴展,但歸根結(jié)底還是在處理****一個類,我們平時開發(fā)的時候,處理一個類是很多,比如常見的helper,Log 之類的。但是這不是我們開發(fā)的重點,我們平時使用最多的還是 Service 類,或者 Repository 類,里邊有很多,各種各樣的的類,比如 User 表,Role 表,Permission 表等等,每一個實體又都對應各自的一個服務類或者倉儲類。
4、那這個時候,我們使用上邊的【工廠方法】還行么?肯定是不行的!因為我們上邊是一個二維體系,EFCoreRepository 、SugarRepository、DapperRepository等等,是這樣的二維,我們現(xiàn)在要做的就是在這個二維的基礎上,再加上一個維度,就是要解決 User 、Role、Permission 實體等等這種一組或者一系列的類創(chuàng)建的問題。
5、那就是今天下邊要說到的【抽象工廠】模式。
注意,下邊的例子可能不太恰當,只是作為理解抽象工廠模式來使用,具體開發(fā)中,可能有出入。
四、抽象工廠模式
生活場景第三集:
老張生活的美滋滋,天天不愁吃,嗯還押上了??,有一天老李也想要廚師,那咋辦,我大筆一揮,簡單!安排上。
我就通知我的個人助理,幫老李也找了三個廚師——中餐,西餐,面點,那現(xiàn)在就有六個了。
就這樣過了一段時間,個人助理辭職了?!
情況就是,他說太忙了,而且需求也是各種各樣,相同的一個系列,需要準備很多,還有我的其他的小伙伴也讓他來找廚師,好幾組,自己的小本本上,密密麻麻的都是記錄的各種安排。
明白了,如果是一系列的,一組對象實例創(chuàng)建的話,如何還用工廠方法的話,就會出問題,而且還同時破壞了OCP原則,大家想想是不是,對內(nèi)修改關(guān)閉原則
那這個時候怎么辦呢,欸!我再抽象一層,把我的這個個人助理,也就是這個工廠,給增加一個大管家中介,這里是不是又有了一個中介者模式的味道??,所以說,設計模式都有相似性。
那這個大管家中介,就是一個抽象工廠,他負責管理多個組多個系列,也就是對應多個私人助理,以及下邊的廚師。
上邊的問題我們都看到了,我們要解決一系列一組類創(chuàng)建的問題,引申出來了抽象工廠模式,那下邊我們就簡單寫一些代碼,看看是否跑的通。
1、創(chuàng)建一個核心層,添加多個倉儲操作
我們畢竟要操作數(shù)據(jù)庫嘛,所以肯定需要倉儲來持久化,那我們就創(chuàng)建一個 FactoryPattern.Core 層,用來存放我們整個項目核心的底層。首先要創(chuàng)建的就是幾個倉儲:
[
](javascript:void(0); "復制代碼")
<pre>/// <summary>
/// 定義抽象的基類倉儲 /// </summary>
public abstract class BaseRepository
{ /// <summary>
/// 創(chuàng)建 /// </summary>
public abstract void Add(); /// <summary>
/// 刪除 /// </summary>
public abstract void Delete(); /// <summary>
/// 修改 /// </summary>
public abstract void Update(); /// <summary>
/// 查詢 /// </summary>/
public abstract void Query();
} /// <summary>
/// 定義抽象用戶倉儲,繼承抽象基類倉儲 /// 抽象的目的,是為了給UserRepositoryEFCore、UserRepositorySugar、 /// 做父類 /// </summary>
public abstract class UserRepository: BaseRepository
{
} /// <summary>
/// 同 UserRepository /// </summary>
public abstract class RoleRepository: BaseRepository
{
} /// <summary>
/// 同 UserRepository /// </summary>
public abstract class PermissionRepository: BaseRepository
{
}</pre>

](javascript:void(0); "復制代碼")
那基本的倉儲都已經(jīng)定義好了,現(xiàn)在就需要一個工廠來生產(chǎn)這一系列產(chǎn)品了,所以我們定義一個抽象工廠類:
[
](javascript:void(0); "復制代碼")
<pre> /// <summary>
/// 抽象工廠類,提供創(chuàng)建不同倉儲接口 /// </summary>
public abstract class AbstractFactory
{ // 抽象工廠提供創(chuàng)建一系列產(chǎn)品的接口
public abstract UserRepository UserRepository(); public abstract RoleRepository RoleRepository(); public abstract PermissionRepository PermissionRepository();
}</pre>

](javascript:void(0); "復制代碼")
結(jié)構(gòu)如下:

2、創(chuàng)建EFCore倉儲工廠層
說人話就是,剛剛我們不是定義了一個抽象的工廠么,用來生產(chǎn)我們數(shù)據(jù)庫中一系列一組的產(chǎn)品,也就是數(shù)據(jù)庫表,那現(xiàn)在我們就需要指定具體的工廠來生產(chǎn)他們了,首先第一個我們就用EFCore這個工廠來生產(chǎn),創(chuàng)建一個 FactoryPattern.Repository.EFCore 類庫,并引用 Core 核心層

首先呢,我們就要在這一層中,對那幾個抽象的倉儲類做重寫,對應每一個EFCore 版本的倉儲類,可能你會問為什么,要每一個重寫下,還是OCP原則,而且還有一個愿意,Sqlsugar 可能某些表達式查詢,在EFCore里不能用,所以必須每一個重寫出來。
這里有一個地方就是,可以在EFCore也針對基類倉儲做一個基類的,但是后來有類型不一致問題,大家可以自己看看.
[
](javascript:void(0); "復制代碼")
<pre> /// <summary>
/// EFCore User 倉儲,繼承User倉儲 /// </summary>
public class UserRepositoryEFCore : UserRepository
{ public override void Add()
{ throw new NotImplementedException();
} public override void Delete()
{ throw new NotImplementedException();
} public override void Query()
{ throw new NotImplementedException();
} public override void Update()
{ throw new NotImplementedException();
}
} // 其他兩個表也是這個情況,不粘貼代碼了。</pre>

](javascript:void(0); "復制代碼")
那現(xiàn)在有了子倉儲產(chǎn)品了,我們就開始加工生產(chǎn)了,創(chuàng)建 EFCoreRepositoryFactory.cs ,并繼承抽象工廠,同時實現(xiàn)抽象方法:
[
](javascript:void(0); "復制代碼")
<pre> /// <summary>
/// EFCore 倉儲子工廠 /// 用來生產(chǎn)各個實體倉儲 /// </summary>
public class EFCoreRepositoryFactory : AbstractFactory
{ public override PermissionRepository PermissionRepository()
{ return new PermissionRepositoryEFCore();
} public override RoleRepository RoleRepository()
{ return new RoleRepositoryEFCore();
} public override UserRepository UserRepository()
{ return new UserRepositoryEFCore();
}
}</pre>

](javascript:void(0); "復制代碼")
結(jié)構(gòu)如下:

3、同理,創(chuàng)建Sugar倉儲工廠層
過程和上邊的一模一樣,我就不多說了,整體結(jié)構(gòu)還是這樣的:

4、控制器調(diào)用實例
我們在 api 層,引用剛剛創(chuàng)建的兩個倉儲層項目:

然后開始調(diào)用:
[
](javascript:void(0); "復制代碼")
<pre>[HttpGet] public void Get()
{ // 實例化工廠,這里用來生產(chǎn) efcore 這一系列的 產(chǎn)品
AbstractFactory efcoreFactory = new EFCoreRepositoryFactory();
efcoreFactory.UserRepository().Add();
efcoreFactory.RoleRepository().Delete();
efcoreFactory.PermissionRepository().Query(); // 實例化工廠,這里用來生產(chǎn) sugar 這一系列的 產(chǎn)品
AbstractFactory sugarFactory = new SugarRepositoryFactory();
sugarFactory.UserRepository().Add();
sugarFactory.RoleRepository().Delete();
sugarFactory.PermissionRepository().Query();
}</pre>

](javascript:void(0); "復制代碼")
結(jié)果我就不調(diào)試,肯定是沒有問題的,畢竟僅僅是類的調(diào)用嘛,如果說你從上邊往下看,看到了這里,還沒有問題,并且能大概明白其中的意義,那你的工廠模式已經(jīng)完全學會了,特別是這個抽象工廠,一直很繞,而且也使用的不是很直觀,用途不是很多。
5、抽象工廠和工廠方法的區(qū)別
現(xiàn)在我們再簡單的說明一下,我們通過【抽象工廠】模式,慢慢的明白了,其實抽象工廠是在【工廠方法】模式的基礎上,往外又多做了一套封裝,目的就是解決生產(chǎn)一系列產(chǎn)品的時候,工廠方法無法滿足的問題。
所以這個時候,如果有人問你二者的區(qū)別,核心的區(qū)別就是:如果是一個產(chǎn)品用工廠方法,一系列產(chǎn)品,用抽象工廠。
創(chuàng)建一個產(chǎn)品用工廠方法;
創(chuàng)建一系列產(chǎn)品用抽象工廠;
但是雖然解決了問題,還是有很多的問題的,單單從上邊我們創(chuàng)建了那么多的子類,以及子類必須重寫這一點來看,這么設計肯定有弊端,那有什么改進的呢?咱們繼續(xù)往下看。
五、抽象工廠與依賴注入
這里我就不詳細說了,其實就是一個思路的用法,這里舉個例子就行了,大家肯定都用過三層架構(gòu),其中有一個數(shù)據(jù)訪問層 DALFactory ,我們平時使用的時候,就是直接把類的實例給 return 出來,如果我們同時連接多個數(shù)據(jù)庫呢?那這樣的話,我們就像上邊說到的,建立多個 DAL 層,比如 DALSqlServer、DALMysql 等等,那我們?nèi)绾瓮ㄟ^接口來獲取服務呢,就是通過反射指定的程序集來實現(xiàn),這個就是簡單的使用了抽象工廠。
比如這個網(wǎng)上的圖片,就是這個意思,大家看個意思就行:

說到這里大家有沒有了解到一些小小的心得,似乎這個和有一個東西很像!對!就是我們平時使用的依賴注入。其實我們可以想一想,我們在服務注冊的時候,通過反射將多個服務注冊到容器里,然后我們再使用的時候,是容器通過接口別名,給我們找到指定的具體服務,甚至也實現(xiàn)了一個接口,多個服務的操作,這個就是工廠模式和依賴注入的小小的關(guān)系。
工廠模式和依賴容器的區(qū)別:
factory,di container,service locator都是對象的創(chuàng)建者或者提供者,為什么是不同的名字,不是因為它們提供對象的實現(xiàn),而是取決于你如何使用。你通過注入的方編程,讓框架利用它提供對象,它就是di container,你顯式利用它來提供你所需要的對象,它就叫做service locator。
總結(jié)一下:
今天我們通過簡單的代碼,一步一步,從【簡單工廠】開始,了解到了多個類是如何創(chuàng)建的,然后明白了【工廠方法】模式,更好的實現(xiàn)了OCP原則,也實現(xiàn)了封裝多態(tài)的原理,接下來咱們通過【抽象工廠】的舉例,進一步對一系列一組產(chǎn)品生產(chǎn)的時候,所采用的方案,到最后,我們簡單的說明了一下反射以及依賴注入和工廠模式的關(guān)系,可能讀起來還是有點兒凌亂,不過我還是簡單大家多多的學學,查查資料,因為我認為,設計模式是結(jié)構(gòu)的基礎,而工廠模式又是設計模式的基礎,可見其重要性,如果看不懂沒關(guān)系,等我直播講課吧。
當然抽象工廠也是有一些弊端的,比如(摘抄百度):
【3.1】、****優(yōu)點:【抽象工廠】模式將系列產(chǎn)品的創(chuàng)建工作延遲到具體工廠的子類中,我們聲明工廠類變量的時候是使用的抽象類型,同理,我們使用產(chǎn)品類型也是抽象類型,這樣做就盡可能的可以減少客戶端代碼與具體產(chǎn)品類之間的依賴,從而降低了系統(tǒng)的耦合度。耦合度降低了,對于后期的維護和擴展就更有利,這也就是【抽象工廠】模式的優(yōu)點所在??赡苡腥藭f在Main方法里面(這里的代碼就是客戶端的使用方)還是會使用具體的工廠類,對的。這個其實我們通過Net的配置,把這部分移出去,最后把依賴關(guān)系放到配置文件中。如果有新的需求我們只需要修改配置文件,根本就不需要修改代碼了,讓客戶代碼更穩(wěn)定。依賴關(guān)系肯定會存在,我們要做的就是降低依賴,想完全去除很難,也不現(xiàn)實。
【3.2】、缺點:有優(yōu)點肯定就有缺點,因為每種模式都有他的使用范圍,或者說要解決的問題,不能解決的問題就是缺點了,其實也不能叫缺點了。【抽象工廠】模式很難支持增加新產(chǎn)品的變化,這是因為抽象工廠接口中已經(jīng)確定了可以被創(chuàng)建的產(chǎn)品集合,如果需要添加新產(chǎn)品,此時就必須去修改抽象工廠的接口,這樣就涉及到抽象工廠類的以及所有子類的改變,這樣也就違背了“開發(fā)——封閉”原則。
【3.3】、抽象工廠模式的****使用場景: 如果系統(tǒng)需要多套的代碼解決方案,并且每套的代碼方案中又有很多相互關(guān)聯(lián)的產(chǎn)品類型,并且在系統(tǒng)中我們可以相互替換的使用一套產(chǎn)品的時候可以使用該模式,客戶端不需要依賴具體實現(xiàn)。
六、示例代碼
https://github.com/anjoy8/DesignPattern/tree/master/FactoryPattern
1、Dependency Injection vs Factory Pattern
一起學習,一起進步 QQ群:867095512
