編程思想 定義過濾的方式解耦
- 2020 年 3 月 12 日
- 筆記
本文將會很少涉及 dotnet 的知識,主要講用定義過濾的方式解除過程業務的耦合。在一些業務上,可以從業務層面或邏輯層面明顯分為幾層,每一層之前的數據相互依賴或處理順序相互依賴,但邏輯都獨立。此時如果將業務處理放在過程處理裡面,將會讓過程處理耦合具體業務。而定義過濾的方式為讓過程邏輯只是搭建框架為主,具體業務通過注入過濾的形式加入到處理
假設我有某個業務需要處理,這個業務分為兩個大步驟,分別是 F1 步驟和 F2 步驟。而 F1 裡面自然有 F1 步驟的業務,同理 F2 也一樣。而我的業務上對於數據處理的過程要求比較高,在過程處理上面的邏輯相對複雜,而如果將 F1 的業務和 F2 的業務放進來,大概的邏輯會是這樣
// 複雜的過程處理程式碼 // 通用的過程處理業務程式碼 // F1 業務需要處理的程式碼 // 其他詭異的邏輯業務程式碼 // 過程處理的業務程式碼 // F2 業務需要處理的程式碼 // 其他處理程式碼
總體看起來邏輯將會比較複雜,同時耦合度也將會比較高
讀到這裡的小夥伴是否有一個疑問,在什麼時候就能定義出過程處理的邏輯,而其中的 F1 業務和 F2 業務是如何能定義出哪些程式碼是屬於哪個步驟
用一個比較具體的例子說明
我需要在 WPF 中處理一個影片文件,影片文件的處理包含了影片文件本身的專業邏輯,也就是如何解碼影片文件,如何將影片文件拼接為一張張圖片。而處理影片文件包含了業務上的事情,業務上的事情包含以下三個部分
- 打開影片文件之前需要看看這個影片文件的編碼格式,如果不清真就提示用戶(判斷格式)
- 每一個影片轉出來的圖片,在拿到數據之前需要做一點點優化,如在數據上添加一個水印(添加水印)
- 從圖片數據轉圖片完成之後,需要隨機拿出一些圖片給用戶預覽(提供預覽)
而如果將上面的業務邏輯混合到整個影片處理邏輯上,大概會是這個樣子
- 打開影片文件,分批加入到記憶體
- 判斷格式(業務第一個步驟)
- 解碼拿到 HEAD 數據
- 載入聲音
- 獲取影片解析度,創建空白數據
- 雜七雜八的專業處理邏輯
- 按照一秒30張圖片組合出影片處理,將影片一秒拆為 30 張圖片
- 以下為影片的每一張圖片處理邏輯
- 解析出影片中的圖片
- 添加水印(業務第二個步驟)
- 將圖片做一些優化
- 奇特的圖片處理
- 保存圖片到文件
- 提供預覽(業務第三個步驟)
- 壓縮處理的所有圖片
- 我也編不出的業務邏輯
大概來說,其實上面的邏輯可以分為三個大部分,第一個部分就是演算法部分,或者說專業邏輯程式碼。這部分主要就是如何解碼影片,如何將影片轉圖片以及優化圖片等邏輯。這些邏輯基本都是很通用的,同時這部分邏輯也應該做到很獨立。業務上使用只是調用方法傳入參數而已,不應該將具體程式碼寫入到耦合某個業務裡面
第二個部分就是定義處理的過程,其實上面的邏輯應該可以分為以下過程
- 從文件載入到記憶體
- 解碼影片
- 從影片轉圖片
- 處理圖片
- 保存處理邏輯
這裡面大部分步驟都是幾乎做到通用的,也就是在這些步驟上只是填充專業邏輯。這部分邏輯適合創建一個 NuGet 庫存放,這樣在多個項目之間都能共用。但是現在的問題來了,我的業務邏輯分散在三個過程裡面
- 文件打開後
- 圖片處理時
- 圖片處理完成
而其中 文件打開後 這個過程在此業務中只用一次。而後面兩個過程將會根據具體影片執行多次
這裡的業務邏輯就是第三個部分。這裡的邏輯劃分其實和程式碼執行順序沒有直接關係,而是根據程式碼邏輯的層次劃分。進一步說,其實第一部分專業邏輯和第二部分定義處理的過程這兩個部分不是緊密的關係。假設咱有很多不同的專業邏輯,如針對不同的影片採用不同的處理方式,但是這些處理方式之前的處理過程是差不多的,也就是第二個部分定義處理的過程部分可以獨立出來,根據具體功能填寫具體的專業邏輯。也就是從層次上,第二部分屬於框架層面,但如果我將第三部分業務邏輯放在了整個處理邏輯裡面,那麼此時的功能就都無法獨立
是否可以讓第三部分業務邏輯分離?其實從上面說的內容,連第一部分都可以分離。假設將第二部分框架層面的部分作為框架使用,那麼框架就是獨立的,但是框架沒有任何具體的功能。此時通過在框架各個部分填補專業邏輯可以讓第一部分和第二部分聯合作出實際的功能
但是想要完成功能還需要有業務的存在,此時的業務就不能寫入到庫的程式碼中。這裡的庫指的是如 NuGet 一樣的程式碼庫,或者說是通用程式碼裡面,通用程式碼不含各個產品的具體業務
既然在第二部分已經可以定義出框架了,那麼可以在框架裡面應用過濾的方式進行解耦。在框架裡面可以定義邏輯處理的順序,在關鍵的處理裡面開放注入介面。如在影片文件打開之後,此時添加一個可以注入的點,可以讓業務層注入業務邏輯
而此時注入的部分的建議是注入一個介面,在框架裡面定義了過程用到傳入的數據,在某些處理的過程裡面可以讓開發者注入具體的實現類,通過介面進行約束和獲取數據進行處理的方式,就是本文說的定義過濾的方式解耦
例如有簡化的邏輯,我的框架的定義如下
interface IFooHandler { void AddF1Filter(IF1Filter filter); void AddF2Filter(IF2Filter filter); }
框架裡面提供了添加兩個不同的業務過濾的方法,而這兩個不同的業務過濾將會在整個過程的不同步驟進行調用,同時也使用兩個不同的介面限定了具體的業務邏輯的注入的類的實現方式。這個方法的優點在於,可以將業務的邏輯放在具體的業務上做,而框架和庫的部分只是做通用的處理邏輯。換句話說是將不通用的程式碼作為介面的方式提出,而在業務層進行注入,注入的方式就是調用框架給出的方法傳入對應的介面實現。如上面程式碼,框架在兩個步驟裡面給出了兩個可以注入的方法,通過調用框架的這兩個方法傳入具體實現就能做到在框架處理的過程調用注入的業務
而對比將所有邏輯都寫在一起的優勢在於降低耦合,原先的業務邏輯可以分散寫,同時還能訪問上下文更多資訊。而現在只能通過約束的介面,如上面程式碼的 IF1Filter 定義,此時將可以讓業務邏輯不影響到框架裡面的邏輯,換句話是框架裡面只需要了解介面的內容而不需要了解具體業務
而這個過濾的方式可以在框架裡面各個地方提供,甚至框架可以通過判斷有注入實際業務和沒有注入的行為,如有注入業務時採用業務特殊方法替換原有的邏輯,如下面程式碼定義
public void SetF1Filter(IF1Filter filter) { Filter = filter; } private IF1Filter Filter { get; set; } = DefaultFilter; private static readonly IF1Filter DefaultFilter = new F1Filter();
如果用戶沒有注入實際的邏輯,那麼將會使用 DefaultFilter 的邏輯,如果用戶注入了,那麼將使用用戶的邏輯
對於部分業務處理,如上面說到的在文件打開之後的處理,此時的處理不應該限定只有一個,於是如上面程式碼定義,可以讓用戶調用方法多次,之後進行每個用戶傳入的業務處理
private static void HandleFoo(IFooHandler fooHandler) { fooHandler.AddF1Filter(new F1Filter()); fooHandler.AddF1Filter(new F1Filter()); fooHandler.AddF1Filter(new F1Filter()); }
如上面程式碼可以讓框架在 F1 步驟處理三次,雖然上面程式碼都是傳入一樣的類創建
而如果將整個注入好了邏輯的框架作為一個實例存放,在軟體中的不同業務使用,此時也許會遇到某些業務需要添加一點功能,而某些業務不需要的情況,如下面程式碼的 fooHandle 是已經注入好了業務邏輯的實例,但是此時我需要在某個業務裡面對他的處理過程進行一點更改
var fooHandle = new FooHandler(); HandleFoo(fooHandle);
此時的修改如果依然對 fooHandle 進行注入不加任何邏輯,那麼此時將會影響到其他使用的業務,這裡在 C# 裡面可以採用 using 的方法,大概的寫法如下
var f1Filter = new F1Filter(); using (f1Filter) { fooHandle.AddF1Filter(f1Filter); // 其他業務 }
在本次處理完成 F1Filter 和業務之後,將會調用 f1Filter 的釋放程式碼,在釋放程式碼裡面可以反過來調用 fooHandle 的移除添加方法
大概實現邏輯請看我的程式碼 這裡面只是簡單定義
本作品採用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議 進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名林德熙(包含鏈接: https://blog.lindexi.com ),不得用於商業目的,基於本文修改後的作品務必以相同的許可發布。如有任何疑問,請 與我聯繫 。