2021年6月18日 星期五

[設計模式] Factory Method 工廠方法 與 Simple Factory 簡單工廠 的差異

先前文章有提到,

Simple Factory 主要目的是用來封裝物件創建的過程,

讓主程式與各種方法類別解耦合,視野可以只有Operation與OperationFactory類別即可。

那為什麼又出現 Factory Method 呢?

原因就在於 Simple Factory 在修改部分違背了 開放-封閉 原則。
















程式碼請參考 GitHub連結 ( Simple Factory在 Chapter_1.2 , Factory Method 在 Chapter_8.1 )

Factory Method 與 Simple Factory 的類別架構如上圖,

實做計算機當作範例,有加、減和乘的功能,

試著想想看,想要再增加新的功能,例如除法,

在使用 Simple Factory 的情況下,那該怎麼做?

三個步驟:

步驟一:先建立OperationDiv Class













步驟二:接著在 OperationFactory 內增加 "case" 敘述來增加判斷


步驟三:最後在main中修改輸入參數








但這樣不就每次的增減都需要去修改 OperationFactory 的內容嗎?

如大話設計模式內所說的,

"不但對擴展開放了,對修改也開放了"

這就違背了 開放-封閉 原則。


Factory Method 則對於這部分進行了改善,

如果以Factory Method的模式想增加新的除法功能,那我們可以怎麼做?

三個步驟:

步驟一:一樣先增加 OperationSqrt Class(類別內容和剛剛的相同)

步驟二:再建立 DiveFactory Class,用來產生OperationDiv物件









步驟三:最後在 main內把operFactory物件new出DiveFactory即可









有沒有發現兩者的差異?關鍵在於步驟二。

使用Simple Factory的情況下,新增了除法的功能,必須改動既有的程式碼( OperationFactory)。

使用Factory Method的情況下,新增了除法的功能,

卻都沒有對原本既有的程式碼進行修改( main() 的程式碼屬於使用者端,不是開發者端),

這就是利用擴展來新增功能(也可刪除功能),符合 開放-封閉 原則。


使用者得自行new物件,還保有封裝"建立物件過程"的優點嗎?

這時候你可能會想說,Simple Factory模式下使用者只要輸入"+"就有演算法物件,

Factory Method模式下還得把Factory new出來才有演算法物件,

那麼 Factory Method 不就失去了Simple Factory封裝"建立(new)物件過程"的優點嗎?

其實 Factory Method 依然保留著封裝"建立物件過程"的優點,

因為使用者還是看不到最底層演算法(OperationAdd、OperationSub、...)被new的過程,

只能看到Factory,降低使用者與演算法物件的耦合,依然達到保護演算法的作用。

只是 Simple Factory 內的選擇判斷邏輯就移到使用者端(main)去執行(自行選擇Factory去new)。

話說回來,其實只要演算法包成dll,那麼也完全看不到啦...

但作者一直強調這部分,也是我自己的疑問之一,目前的理解是這樣,

所以做個紀錄,目前的我還是有點模糊作者強調這個封裝過程的用意。



符合 開放-封閉 原則的好處

有必要為了符合原則而增加複雜度嗎?多了更多的程式碼!

來看一開始兩者的UML類別圖

可以發現 Simple Factory 的 User Vision 只有 Operation 與 OperationFactory,

Factory Method 的 User Vision 卻還需要多看到(include)四個演算法的工廠,

{AddFactory , SubFactory , MulFactory , DivFactory}

這樣子複雜度不就又提高了嗎?

沒錯,複雜度提高了,但卻符合 開放-封閉 原則。

舉個例子,

以Simple Factory去開發的時候,

想像一下每一個類別都各自由團隊中的一位工程師所負責開發的,

當你在負責 除法 功能類別的時候,寫完了要更新到專案中,

但你此時卻必須去改動別人所負責的OperationFactory類別,客戶才能使用你的除法功能,

假使:

  • 負責OperationFactory類別的人請了長假,不開放給人修改
  • OperationFactory是個龐大的類別,你也不知道該改動哪裡才OK
  • 客戶急著要功能,你也趕工完成,但你一時改動不了OperationFactory(沒授權、不熟悉、不敢改),導致功能無法上線

[這些例子可能不是最適切的,如果有更好的情境歡迎提供。]

如果有以上情境,那是不是要等到負責人回來才能把 除法 功能給使用者使用?

但如果使用 Factory Method ,完成自己的部分之後將軟體發佈出去,

使用者便可以使用最新的功能,不需要修改到其他工程師負責的部分。

唯一的缺點,大概就是需要撰寫新增的功能類別之外,

還得寫對應的 Factory類別,工作量會多一點。


適用情境

以我的經驗來看,如果進行的專案已經是成熟的專案,且變動不大,

例如客戶下訂的是公司內部已經發表一段時間的穩定機台,

那麼該機台內部使用的硬體、元件也相對的固定,臨時替換不同廠牌的可能性不大,

不大可能擴充新的元件進來,這時我會建議使用 Strategy + Simple Factory 作為架構,

把可能使用到的元件都寫進去,方便進行抽換。

但是如果今天進行的專案是嘗試性質、測試性質,

對於機台既有的規格都很模糊,可能嘗試的元件有很多種,需要擴充的機會比較多,

例如目前有三台相機需要做評估,但不確定是否這三台就能滿足需求,

可能還會測試更多品牌的產品,那麼使用 Factory Method 作為架構來開發,

可以保有擴充的彈性。


以團隊分工來看Factory Method

對於團隊分工來說,只要 IFactory 這層介面定義的完善,

就可以讓寫主架構的人不必顧慮負責相機類別的人怎麼實作,

只要能滿足 IFactory 介面所定義的 function 即可。


Factory心得歸納

目前學習了兩個工廠模式,

而工廠模式主要的功能就是讓使用者和產品類別(以上面的例子就是演算法)解開耦合,

只要是工廠,就是為了達成封裝產品物件建立的過程,它的目的是協助產品物件的建立,

而不是產品物件本身的功能。


大話設計模式-工廠方法章節閱讀心得

在110頁和111頁的範例,我覺得情境有點不相符。

作者說為了改善"改一個程式碼就改變所有物件的型別"  所以改成 Factory Method,

但其實簡單工廠模式那個範例(p110)只需要把輸入參數變成變數,

就可以改一次就把全部物件改變型別了,這是第一點。

第二點是,他(p111)的範例其實和(p110)的範例是不相等的,

(p110)簡單工廠的範例是實作三個物件去做三件事情,

但(p111)的範例是實作一個物件去做三件事情,兩者範例觀念其實並不相同。  

不一定正確,只是提出了我的疑問。


拖了好久,終於把想寫的完整地寫完了。


============================================================

110/06/18與我的研究所學長Vincent 討論後又有不同的心得,在這邊也補充上來跟大家分享。

本來想融會貫通後直接修改我原本的內容,但學長的舉例都滿完整的,

因此另外記錄在這邊,[ ]內是我的補充。

已知與未知的開發需求數量決定要使用哪一種模式

你在設計 Simple Factory 的時候你無法預估之後會不會加入其他的操作

下次會不會有"/" "\" 甚至是"asdfasfsd"?你設計期無法知道,

所以在這層面上的需求的話,Simple Factory無法滿足大量"未知"的擴充

你在設計得時候那些新東西都還連個影子都沒有,只是"可能"需要大量擴充,

[對於擴充這一點需求,可能性的高低影響了不同模式的選擇。]

而 Factory Method 在我把專案release出去後,其他人仍可以沿續設計以低成本擴充,

Simple Factory就非得把判斷邏輯拆開,是寫死的。

[在這邊我的理解是,其實 封裝物件建立,這件事情,是簡單工廠把選擇判斷包裝起來,而工廠方法雖然也有包裝,但不是它真正的重點,它的重點是擴充的方便性,兩者的目標不同。]

Simple Factory 無法在事後不動到Simple Factory Class的情況下,順利產生新的operation,

但這也是它的重點,實務上我覺得 Simple Factory 跟正式的工廠Factory Method相比,

使用數量前者應該是遠大於後者,大部分的需求通常可以在設計期列舉出大部分來,

開放"會變"的,那如果"生產"本身其實不太有變化性 或是事前都已經知道有幾種的話,

那彈性配合多種"生產"就是一個over design,因為未知的新東西不一定會出現,

[出現機率也低的話  確實是多做工  別人也不好理解這段code。]

因為說真的,開發期改simple factory大概也就是加三行東西在switch裡面而已。

情境上是,產品化之後,你們的功能已經配合之前的手臂、機台設計好架構,

且明確知道實際佈署時廠商選用的硬體不一定是你們列舉的那幾種,

問題點就是 你們明確知道有"未知"的擴充需求,在佈署時想最小化"擴充"的成本

甚至開方第三方(USER)可以自行新增,這樣在開發末期把東西統整成正式的工廠,

去應付"已知"擴充需求但"未知"擴充所需的步驟(create obj)的時候

舉例來說,就像你做POS機,你知道要點餐打發票,你也知道要應付各種店家需求,

一定會有你預先沒設計過的餐點,這是已知的未知

[我認為已知的未知是工廠方法的最佳使用目的。]

能封閉的是點餐流程跟,要開放的是開放事後能新增店家自訂的餐點項目,

點餐需要的是產品介面,不是生產介面,把"產品"跟"生產"切開來,

所以設計原則可以歸納為 封閉"產品"跟"結帳流程",用工廠模式開放"生產",

簡單工廠完全沒辦法彈性應對POS機的情境,

VTK用工廠也是類似,封閉rendering pipeline,開放已知的未知,

使用情境結論
  • 已知+  已知的未知(或少量未知) -> Simple Factory,成本也低
  • 已知 + 大量未知 -> Factory Method
以管理者的角度來看這兩個模式

然後合併上述管理面,如果這個未知只是暫時性的,

我是覺得就不需要真的動用到工廠,建置成本太高了。

管理側可以從另一端思考,你的CODE是暫態嗎? 

暫時需要而已的話,短時間內改掉是可以考慮的。

常態的話,不是應該適時的去review設計,若功能是重要的應該是從介面上去調整,

而非套用pattern去滿足暫時性的需求這樣。

該很多開源都會有類似的需求,我們最熟悉的就是VTK開放許多開發者貢獻程式碼進到lib,

所以官方書中會告訴你怎麼實作跟符合他們的工廠模式,但整體來說開發時,

系統複雜度會變高,這個代價是否值得就要評估後再決定。

大多數的狀況下,單純的簡單工廠或是多型應該就能滿足需求,只要定義出介面即可。


沒有留言:

張貼留言

社會新鮮人如何投資?

我的觀點是,在 沒有很多 本錢 的情況下, 別寄望每個月幾千元放到股票或者最近很夯的高股息ETF就能讓你致富, 先投資自己,讓自己的本業收入提高吧。