工廠方法 軟件設計模式 創造性的設計模式

Software -丁哥开讲
709 位粉絲
1 觀看·2022年11月19日
00:30 工廠方法
01:04 概念
01:40 物流系統
03:50 非常糟糕的代碼
07:09 運輸接口
10:30 代碼進行重構
12:32 邏輯是公用
14:09 很典型的區別
17:13 圓形的按鈕對象
19:18 遵循相同的一個接口
22:34 優缺點
24:26 一組工廠方法
26:23 客戶端進行調用
大家好,我們這個視頻來聊一下工廠方法
工廠方法是軟件設計模式當中創造性的設計模式
我們首先來看一下它的概念
它創建對象的模式是這麼玩的
它是在機類中,它有一個接口
然後它來創建對象
同時呢
它允許子類去決定你到底要創建什麼樣的對象
這是工廠方法的一個運作模式
它作爲創造性的設計模式
它的方法就是在機類中提供一個接口來創建對象
然後你的子類去派生這個機類
然後去重載我們的接口來創建你想創建的對象
我們來看一個具體案例
我們就設定一個場景,物流管理系統
我們開始開發這套系統的時候
我們肯定要有一個實際的業務需求
那個時候我們的業務需求就是
我們只有卡車
那我們就以卡車作爲我們的開發方向
所以我們的代碼裏主要是處理關於卡車的一些邏輯
但是經過一段時間以後
我們這個應用程序變得很流行
經常收到很多來自海上運輸公司的一些請求
他們的運輸工具就是船舶了
他們要求你們把我們船舶這些交通工具
納入到你們的應用程序當中去
這本身是一個好事情
因爲我們得到了市場的認可
但是我們發現一個問題
什麼問題
我們原先的代碼都是跟卡車相關的一些處理
耦合在一起的
我們要是添加船舶的話
我們就在我們代碼裏頭
添加一些條件判斷
如果是卡車
我們就做這個
如果是船舶我們做那個
對不對
就這麼玩
這會涉及到什麼
對原來跑得很好的那些代碼進行整體的修改
這種做法
對於說我們前面的系統跑得好好的
做這種整體的修改是風險很大的
因爲我們這種if-else會增加你整個代碼架構的複雜度
最終我們會得到非常糟糕的代碼
爲什麼
裏面充斥着對於運輸對象的類比
進行判斷的這些分支
導致你的整個代碼的重複性
就是邏輯的重複性非常多
然後你的程序
你比如說請程序員來看你的代碼
一看哇
開始你改一點點是吧
你可能有一百個地方
改一個地方OK
但是如果每天都改一百個相同的一種事情的時候
對任何人來說都是一種折磨
因爲我們是人類
我們不是機器
對吧
你一直在做這種糟糕的重複勞動
我們是沒有任何愉悅感和成就感的
那工廠方法模式在這個時候會幫到我們
它是怎麼來幫我們的
它就會
就建議我們調用一個特殊的工廠方法
來代替直接的對象構造調用
我們創建一個對象
一般來說還大部分編成語言
像C++, Java, C++等等
很多流行的語言
比如說使用NEO操作服
或者直接去用什麼方式創建對象
就這種創建對象的方式
就是即使我們使用工廠方法
我們還是要這麼幹
對不對
但是我們通過工廠方法
把你原先
偶合在你實際業務代碼裏的
創建過程拿出來
創建對象的方式還是那樣
但是工廠方法中
你有了另外一個接口以後
你就把這個創建對象的過程
放到工廠方法裏面去
由工廠方法返回的對象
通常我們稱之爲產品
好
那在這裏我們就思考一下
是吧
就這種改變是不是
反正都是創建對象
你只是挪了個地方
沒有意義
對不對
因爲實際上我們只是把
構造函數的調用
從程序的一部分移到另一部分
但是你考慮一下
現在你可以在子類中
承載工廠方法
什麼概念
你挪到
把你創建的對象的過程
拿到你的子類當中
是吧
這個時候你可以做出一個修改
就是你的子類來決定
你要創建什麼樣的產品
你是用卡車
還是用船舶
你在子類裏去決定
當然在這個地方
有個小小限制
只有當這些產品
有一個共同的機類和接口的時候
因爲前面我知道了
工廠方法它一個基本前提
就是你要有一個機類
然後有一個可以就共用的接口
這個時候子類才能返回
就是所謂不同類型的產品
但是即使不同類型的產品
它也追求一個共有的機類
就這樣子
我們工廠方法用起來
纔是最舒服的
當然你可以說
我返回不同類型
我不用什麼機類
行不行
當然是可以
只是沒用到家
好
另外就是機類中的工廠方法
應該什麼
就是返回的那個類型聲明
就是一般我剛纔說了
是吧
就是你有一個機類
這個機類是你自己
對定義的你的產品的
一個共性的一個東西
當然有的人說
我就是普通對象
行不行
當然也是可以
這樣子你在裏面
要傳一些參數
這樣子會讓你的
這個工廠方法可能會比較亂
好
我們繼續講
就是卡車和船舶這種情況
是吧
它都應該實現什麼
運輸接口
因爲它都有運輸的能力
是吧
在就是這個方法
我們比如說稱之爲deliver
然後每個類
都以不同的方式實現這個方法
你卡車就通過陸地來運送貨物
船舶通過海洋來運送貨物
對吧
然後你道路運輸
這個類的工廠方法返回的是什麼
卡車對象
然後你的海洋
這個服務類
對吧
這個工廠方法返回的是
船舶
這叫你
這個不同的工廠方法返回
不同的對象
那使用工廠方法的代碼
是吧
通常我們稱之爲什麼
客戶端的代碼
它不會看到各個子類返回的
實際產品之間有什麼區別
客戶端呢
把所有的產品都當做抽象的運輸工具
就是transport
我不關心你是卡車
我也不關心你是船
你只要給我完成運輸就行
我直接教你方法
這個運輸方法
我不關心你裏面怎麼搞的
就客戶端知道
所有的運輸對象
都應該有deliver的方法
運輸方法
但具體如何操作
客戶端並不關心
好
那我們看一下整體的一個結構
是吧
這裏面就是涉及到了什麼
產品接口
這個我們要聲明一下
是吧
就是所謂產品的那個積累
就該接口呢
就對所有可以由創建者及子類產生的對象
來說
就是通用的
那我們具體的產品
也就是所謂的子類了
從產品開生的
是產品接口的不同的實現
創造者類
就聲明瞭返回新產品對象的共產方法
這就是我們共產方法在哪裏寫了
是吧
重要的是什麼
這個方法的返回類型
一定是產品
就是我們前面那個產品
那是積累
然後你可以將共產方法聲明爲抽象的
以便強制所有的子類
必須實現他們自己的版本
是吧
就是你每個子類
你不要偷懶
是吧
你就只聽你自己的
這是一種強制
子類必須要實現自己方法
共產方法的一種方式
當然有的人說
我就是要用積累的
也是可以的
這個要具體來看
你想想上面我們提到的這個運輸工具
那肯定是不一樣的
這種情況下
你就沒必要什麼使用積累的
你肯定要做自己的版本
就作爲一種替代方法
基礎共產方法
可以返回一些默認的產品類型
上面我們就產品的積累類型就行了
這時候我們注意
儘管他的名字叫產品創建
但是產品創建並不是什麼
創建者的主要責任
這個時候一定要注意
因爲你作爲創建者
你可以做很多事情
其他事情是吧
通常情況下
創建者這個類的話
他已經有一些什麼
產品相關的核心業務邏輯了
尤其是我們
要對現有的代碼進行重構的時候
是吧
他已經有些很多代碼就都在跑了
這個時候我們添加共產方法
可能就是隻是很少的一部分
這就共產方法呢
有助於將這種邏輯和具體的產品
類別結合
就我們打個比喻是吧
一個大型的軟件開發公司
可以有一個程序員的什麼
培訓部門
但是整個公司的主要功能還是幹嘛
編寫代碼
開發產品
而不是培訓程序員
這是一個
一個一個比喻
就是你在這工廠就創建類裏面
添加了一個共產方法
他不是說我就是要創建這個類
你原先的那些邏輯還可以繼續保留的
實體的創建者
重啓了基本的共產方法
他返回
所以他能返回不同類型的產品
共產方法呢
他不一定要一直創建新的實例
他也可以什麼
他可以從緩存
對象池或者其他來源
返回現有的對象
這個是具體問題
具體分析的
好
你比如說我們現在
有這麼一張圖
這張圖裏頭
就是關於UI
就跨平臺UI
相關元素的一個開發設計
我們有基本的
對話框類
他使用不同的UI元素來渲染
他的窗口
在不同的操作系統下
這些元素看起來有點不同
但是他們的行爲應該是一致的
你比如說Windows上的按鈕
到了Linux上
他還是一個按鈕
對不對
當這個共產方法
這個發揮作用的時候
就是你不需要爲每個操作系統
重寫這個對話框類的邏輯
這個邏輯是共用的
我們在這個基對話框類裏面
就是他的激勵當中
就聲明瞭一個產品按鈕的
共產方法
我們就可以在以後創建一個子類
從這個工廠方法裏面
返回Windows風格的按鈕
同樣的Linux也是一樣
返回Linux風格的按鈕
這個子類呢
繼承了激勵的大部分的代碼
但是由於工廠方法的存在
他可以在屏幕上呈現
就特殊的風格按鈕
就爲了使這種模式發揮作用
就是對話框的激勵
必須與抽象按鈕一起工作
就是一個激勵
或者一個具體按鈕
都遵循的所謂接口
這樣呢
無論與哪種類型的按鈕一起工作
就是對話框內的代碼
都能夠保持原理的這些邏輯
當然了
你也可以將這種方法
基於其他UI元素
來進行變通
是吧
但是你在對話框中
每增加一個新的工廠方法
你就離抽象工廠模式
越來越近了
這裏面就提到了
抽象工廠的模式
工廠方法和抽象工廠模式
非常近
它的一個區別
就是抽象工廠
它可以創建很多很多的對象
而工廠方法呢
它是你當前的工廠子類
只創建一個對象
這是一個很典型的區別
當然它還有另外一個區別
就是因爲它是在子工廠裏面
去創建對象
也就是把創建對象的過程
下沉到子粒當中
抽象工廠呢
是直接在這個抽象工廠裏
去創建對象
在不同的接口裏面
創建對象完了以後返回
好
那我們來看一下
就是什麼情況下
用工廠方法比較好
是吧
首先一種
就是當你事先不知道
你的代碼應該
跟你這個就是合作的對象的
確切的類型和依賴關係
不清楚的情況下
你應該使用工廠方法
就工廠方法呢
將產品構造這一塊
是吧
以實際使用的這一塊
這個這塊代碼分開
就是獨立於代碼的其他部分
用來擴展產品構造代碼
就這個時候
因爲你擴展是
就是創建是創建
使用是使用
就是各自進行擴展的時候
就比較容易
就不會互相影響
就是要在應用程序中
添加一個新的產品類型
你只需要什麼
在新的創造者子粒中
就是重寫他的工廠方法
就可以了
這樣子
你就在新的工廠方法裏面
返回一個新的類型
其他的邏輯
怎麼去使用它
你還可以照常運行
好
我們再看下一個
是吧
當你想爲你的
就是你現在的庫啊
框架用戶提供一個
可以擴展內部組件的方法
這個時候你使用
框架方法也是很方便的
就是繼承呢
就是可能是創建一個庫和框架
默認行爲的一個最簡單的方法
但是呢
框架如何識別你的子粒
應該被使用
而不是直接使用
那個標準組件呢
就這個時候
你怎麼去解決啊
這裏面的方法呢
就是說將整個框架中
就構造組件的代碼
提到一個工廠方法當中
並讓任何人
在擴展組件本身的
同時覆蓋這個方法
這樣子你用這個方法
你就可以創建
就是你想要創建的那個對象了
讓我們具體看一下
它怎麼運作
想象一下
你用一個開源的UI框架
寫了一個應用程序
然後你的應用程序中
有圓形的按鈕
但是那框架呢
只提供了方形的
這個時候你用一個
圓形按鈕的子粒
來擴展你的標準的按鈕類
現在你需要告訴
你的主框架類
使用新的按鈕
而不是使用默認的
就爲了達到這一點
你要從機類
框架類裏面
創建一個什麼
一個圓形按鈕的工廠類
它要重載你的工廠方法
這個時候你返回
這個按鈕對象的時候
你要返回什麼
圓形的按鈕對象
那現在使用的就是一個
就是你的圓形按鈕的
那個工廠類
而不是什麼
你原來那個默認的
那個UI框架類
它是這樣的來玩的
好
另一種情況是吧
當你想什麼
通過重複使用現有的對象
而不是每次的
就都重建
他們來節省
這個系統資源的時候
也要使用工廠方法
剛纔我們提到了
是吧
工廠方法不一定非得
每次要這個創建對象
你可以從緩存裏
什麼對象吃
等等是吧
尤其處理這種大型的
資源密集性的對象
是吧
數據庫連接
文件系統
網絡資源
經常遇到這種需求
就要重用一個現象
我們必須做什麼
第一
首先我們要創建一些
就存儲結構
來跟蹤
所有創建的對象
是吧
當有人要請求一個對象的時候
程序應該在一個
對象池裏去找
找一個空閒對象
然後返回給客戶
如果沒有的話怎麼辦
那就創建一個新的
現在以後創建完了
然後返回給客戶
同時把它加到
對象池裏
當然了
這塊代碼
就形成一個套路了
是不是
就必須要全部
放在一個地方
你不用重複
重複這樣的代碼
你在一個地方就可以了
可能最明顯
最方便的方法
就是我們
就是要重用對象的
所有
它的類
它的勾搭函數
然後根據定義的話
勾搭函數必須什麼
必須返回新對象
它不能返回
限制實力了
但是你需要有一個
能夠創建新對象
和重用現有對象的
常規方法
就在這種情況下
其實工廠方法
就是比較合適
這種場景
好
那我們來看一下
就是如何來做
是吧
剛纔也提了幾種方法
首先就是所有的產品
要就是遵循
相同的一個接口
這些接口呢
就是會聲明
每個產品都有
就是都需要的一些方法
對吧
然後在那個
創建者內容中
添加一個空的工廠方法
就這個方法的返回類型
就是通用的產品接口
就是這個
這個是肯定的
然後在創建者的代碼中
要找到所有
對產品勾搭函數的引用
就一個接一個的
用工廠方法的調用
來取代它們
就你不要
直接用勾搭函數的接口了
是吧
然後將這個產品
創建代碼
提取到工廠方法當中去
然後你可能需要
給工廠方法
添加一個臨時的參數
就是你返回
這個產品的類型
這個是要具體
問題具體分析
一般來說
我們直接返回
具體的產品類型
就可以了
因爲就每個工廠
對應的是不同的產品
當然有些情況是吧
你真的把一個工廠方法
通過參數的方式
返回不同類型
這也是可能的
在這一點上
就工廠方法的代碼
看起來就會非常難看
這可能是會有一個
大的開關語句
來選擇實體化產品
我們這種情況
我們先不擔心
就是我們先完成
你想要的功能
我們再重構
是吧
現在就是在工廠方法中
列出每種產品類型
創建的一個
創建者的子類
是吧
在子類中
就存在工廠方法
然後在基礎方法中
提取實際上的結構代碼
就有些公衆代碼
你就放到
這個基礎方法裏面去
如果產品類型太多
然後所有產品創建
就爲所有的產品
創建子類的話
可能意義不大
對吧
這個時候我們
這就是爲什麼要使用
這個參數
你們控制參數
你們可以想象
我們有下面的
類的層次結構
比如說我們有個
郵件的積累
是吧
還有幾個子類
有什麼航空郵件
然後有地面郵件
是吧
然後運輸
有什麼飛機
卡車
火車
然後航空郵件
只有飛機對象
是吧
但是路上的郵件
它可以有很多
是吧
可能卡車
然後火車都可以
你可以創建一個新的
子類
比如說火車郵件
來處理這兩種
這樣的一種情況
還有另外一種選擇
是吧
就是你剛纔說的
那種傳參數這種
然後客戶端代碼
可以像什麼
這個陸地郵件
類的這種工程方法
傳遞一個參數
來控制它
想要產生的產品
你剛纔不是說
這個陸地的
有這麼幾種
你比如說你
你傳卡車或火車
是吧
你分別得到卡車
或者火車
這樣的郵件
這也是可以的
如果在所有的提取之後
是吧
基礎工廠方法
已經就變空了
你就可以把它變成
什麼抽象的
如果還有東西留下
你就把它
把它變成一些
默認的一些行爲
好
我們來說一下
這個優缺點
是吧
首先優點
就是它避免了
創造者和具體產品之間的
這種緊密偶合
這是第一條
再一個
它遵循了什麼
單一職責原則
你可以把產品
創建代碼
轉移到程序的一個地方
使得你的
你的創建部分
是吧
跟你使用部分分離
這樣使你代碼
更容易維護
再一個
它遵循的是吧
開放封閉原則
你可以在程序中
引入新的產品類型
不會破壞
現有的客戶代碼
你的使用的那一部分代碼
就不需要動
代碼可能
有可能會變得更加複雜
這是它的缺點了
是吧
因爲你需要引入
很多的新的子類
來創建
你的新的類型
這是我們工廠方法
這種模式的一個特點
是吧
最好的情況是
你在現有的
創建者類的層次結構中
引入這種模式
反正我們還是要
具體問題來具體分析的
接下來我們就看
它跟其他設計模式的關係
許多設計
從使用工廠方法就開始
因爲它相對來說比較簡單
對不對
它通過子類來進行定製
然後呢
我們剛纔提到了
跟抽象工廠比較接近
緊接着
它跟原型模式比較接近
這種生成器模式比較接近
它們相對來說
更靈活更復雜
逐步發展
然後抽象工廠類
通常它也會有一組工廠方法
這個時候
你也可以使用
這種prototype這種組合
這種方式來使用
你以前的那些方法
然後在你新的對象裏面
添加你想添加的
一些獨特的東西
這是一些變通
然後呢
你可以將工廠方法與迭代器
一起使用
讓集合子類返回
與集合兼容的
不同類型的迭代器
就是跟迭代器模式的一種結合
然後原型模式
它不是基於繼承的
剛纔提到了
所以它沒有這個問題
另一方面呢
就是原型模式
prototype這種模式
需要對克隆的對象
進行復雜的處理化
這個工廠方法呢
是基於繼承的
它跟這個原型系統
這一個區別
它不需要什麼初始化的步驟
另外一個工廠方法
是模板方法的一種特殊化
一個工廠方法
可以作爲大型模板方法的
一個步驟
這是跟其他設計模式的
一些關係
好
那我們就快速過一下
這個代碼例子
是吧
我們首先一個創建者
它有一個工廠方法
就是這裏面
我們就按書面意思
就隨便把這個工廠方法
列出來
然後在這個creator裏面
它還有什麼
其他的一些操作
使用工廠方法
然後再做些
對這個對象
做一些操作
然後對這個創建者呢
我們又有幾個實例
是吧
實例化一實例化二
然後他們分別會有
一個工廠方法
分別返回產品一和產品二
那對產品一和產品二
它又會有一個積累
對吧
這個產品的積累裏面
有一個抽象的方法
就是操作
這個這個方法
然後我們就對這個
就每一種產品
是吧
做不同的這個
尤其是操作上
做不同的處理
繼承完了以後
我們在客戶端
進行調用的時候
我們可以
就是比如說
創建者一
它返回的就是產品一
然後做產品一的一些操作
同樣的
使用創建者二的話
它就會返回產品二
然後做產品二的一些操作
好
那這個就工廠方法
這個設計模式呢
我們就講這些
如果大家是新來的
是吧
別忘了關注丁哥的頻道
讓我們一起
共同欣賞代碼的美妙
謝謝大家
