[.NET]重構之路系列v13–避免直接相依外部組件,以加解密為例

[.NET]重構之路系列v13–避免直接相依外部組件,以加解密為例

前言

在開發每個系統時,難免一定會有某一些模組或功能,相依於外部元件或服務。

但往往在許多貪快的設計中,總是直接相依於外部元件或服務,導致該模組或功能,未來想要換掉或升級時,原有系統改不動的痛。

這一篇文章就從 context 端直接相依於外部加解密組件為出發點,來說明如何把這段相依性隔開。基本上運用的原則,仍然是 IoC 而已。(另外一種常見的場景,是 Log 的功能,大家也可以參考一下 Clark 這一篇文章:[Architecture Pattern] Inversion of Logging

 

直接相依外部組件的情況

請見下圖:

image

這邊 context 指的是一個 Console Project,參考了 SecurityMoudle 這顆 dll,並且在 Console Project 中,需要用到加解密的功能時,都直接使用 OutSideSecurity 的靜態方法。

相依性如圖所示:

加密直接相依解密直接相依

這樣一來,未來想切換加解密的演算法,要改的程式碼相當多,甚至新的組件版本連方法簽章或 namespace 都改變了。

 

以 Context 角度,應用 IoC 來隔離相依性

怎麼運用 IoC 來進行重構,讀者們也可以先參考一下重構系列前面的文章:[ASP.NET]重構之路系列v4 – 簡單使用interface之『你也會IoC』

但是,在實際專案上,往往外部組件是不能改變的,以這篇為例,您可能無法先定義一個 interface,再讓 SecurityModule 中的 OutSideSecurity 去實作該 interface,甚至把 static method 修改掉。

所以,設計 interface 比較正確的方式,應該是以 Context 系統為主,而把與外部系統相依的部份,定義出 Context 端所希望使用的 interface。

接下來來重構我們的範例 (實務上,請記得先加上測試程式,以確保重構之後的程式,仍能執行出正確的結果)

image

先從 context 端來了解,究竟要做什麼事。以這例子來說,就是「我有一串密文,希望可以取得解密後的結果」。

因此,訂出一個 interface: ISecurityService

接下來,把原本解密的部份,改相依於 ISecurityService ,呼叫 ISecurityService 上的 Decrypt 方法。

這邊可以注意一下,對這裡的 context 來說,只在乎密文解密。在 Program 這一個 class 上,取得加解密使用的 key,應不屬於 Program 的職責。所以,interface 上的方法簽章,會變成下圖:

image

Context 其實這樣就搞定了,怎麼給予 interface 的 instance,則交給工廠決定。

等等…工廠決定回傳實作 interface 的 instance?可我原本用的是靜態方法,而且沒法子修改外部組件啊。沒錯,這時候只需要一個轉接的 class 即可。

工廠方法回傳型別為 ISecurityService,而實際回傳的則是用來轉接的 class:OutSideSecurityAdapter。

image

而 OutSideSecurityAdapter 中,實際用來加解密的模組,則是使用 SecurityModule 中的 OutSideSecurity,請見下圖:

image

透過這樣的方式,即可讓整個 context 的系統中,需要用到加解密的地方,都只相依於自己系統中定義的 ISecurityService,而實際用來加解密的模組,透過工廠來決定與初始化。而外部組件與此 interface 不符合的部分,則透過一個 Adapter 來做轉接。

 

Adapter Pattern

「轉接」,當外部組件無法滿足系統內所使用之 interface 時,應發動 Adapter pattern

結構請見下圖:

adapter-pattern

出處:http://www.oodesign.com/adapter-pattern.html

這邊的 Client 即為 Program,Target 則為 ISecurityService,Adapter則是 OutSideSecurityAdapter,Adaptee 則是 SecurityModule 這顆 dll 的 OutSideSecurity。

 

系統邊界的概念

最後,透過這樣的方式,這些原本直接相依外部系統的關係,最後就是僅相依於自己系統中的 interface,而這些 interface,就可以將其視為系統的邊界。

邊界的概念,可參考六角架構(Hexagonal Architecture),在該文章上有兩張圖描述遞相當清楚。


(出處:http://www.duncannisbet.co.uk/hexagonal-architecture-for-testers-part-1

文中也舉一個登入驗證的例子,如下圖所示:


(出處:http://www.duncannisbet.co.uk/hexagonal-architecture-for-testers-part-1

重構完的相依圖形如下:

最後的相依圖

 

結論

經過上面這樣的重構之後,未來要抽換外部的加解密演算法模組,就相當簡單。只需要新增一個新的 Adapter,把新的加解密演算法當 Adaptee,接起來,在工廠中轉接到新的 Adapter 即可。原本系統中 context 程式碼,都無須做任何更動。

刻意不在文章前面提到 adapter pattern 的原因,是希望讀者朋友們可以理解到,design pattern並不完全只是用來讓系統設計的更彈性,更重要的是,它用來解決什麼樣的問題。

而這些 design pattern,之所以被稱為 pattern,就是因為這些問題在許多的系統中,不同的語言中,都很常發生。進而淬鍊出一個最普遍化的抽象模型,用來解決最普遍的問題。往往有許多問題本質相同,但表面或因其他組合,而有些許差異。所以,運用 design pattern 時,也千萬不要墨守成規,以為設計出來的 class diagram 一定要長得跟 GoF 上的一樣,才叫做 design pattern。

重點還是在於,運用其精神、基本原則,解決問題的根本。是什麼 pattern,只要可以溝通,就沒那麼重要了。

另外一個切入點就是,當您壓根不會碰到這樣的問題,卻硬要把這彈性做進去,那就是 over design,為了 pattern 而 pattern,有些人甚至以此沾沾自喜,認為自己實在太會 OO,其實根本就只是在荼害後人。

切記,解決問題才是重點。了解基本精神才是重點。滿足了基本原則,確保未來需求、程式、技術的異動,不會傷筋動骨,才是重點。如 Clark 在噗浪上所說的一句話,我也深有同感:「不需要在設計時,硬想使用新技術,而是要規劃出能抽換新技術的設計」。(Clark 原文為:不需要設計使用新技術,是要設計能抽換新技術)


blog 與課程更新內容,請前往新站位置:http://tdd.best/