【原創】如何寫一個框架:模式

 

模式

 

接下來去聊一聊框架設計中的一些常見設計模式,這和傳統的一些設計模式不一樣(以前寫過 無廢話C#設計模式系列文章,有興趣的讀者能夠去看一下),這裏聊的一些設計模式是比較高層的粗粒度的架構設計模式,主要是用於以前說的構建框架的龍骨,使得框架中的幾百個類型能夠有結構有條理組織在一塊兒,在這些模式之中你徹底能夠再去使用各類Gof設計模式,這是不衝突的。 html

 

分層

 

在以前的文章中其實已經強調過這個概念了。即便咱們寫的是同一個框架,對於框架中的全部類型可能也會有多層的結構: 設計模式

  1. 純抽象類型(接口),用於定義框架的骨骼,好比IController
  2. 半抽象類型(抽象類或帶默認實現的接口),用於定義框架的骨骼而且仍是共有邏輯在載體,好比AbstractController
  3. 具體的實現類型,在骨骼的基礎上的不一樣的實現,好比VelocityViewEngine

在一般狀況下是能夠把這些類型都放到一個模塊內方便閱讀和管理,在一些特殊的狀況下咱們能夠把框架直接在層次上拆分紅多層多個框架(這些父子框架之間能夠繼承關係),好比: 網絡

  1. 若是有太多具體類型是過於具體的和業務邏輯相關的實現,並且實現的數量很是龐大
  2. 框架雖然總體解決的是一個問題,但針對不一樣的環境(好比網站、桌面和移動)在實現上有比較大的差別,那麼咱們能夠把共同的部分放到父框架中,爲不一樣環境提供單獨的擴展框架
  3. 若是框架會和某個業務邏輯進行銜接,若是業務邏輯的接口不穩定的話,徹底能夠把這種不穩定的因素隔離出一個單獨的子框架,保持父框架的問題

總之,OO的概念不必定能夠應用於類型之間的關係,還能夠應用到框架自己的層次關係上。 架構

 

處理器和過濾器

 

框架就像是空氣淨化器的主框架它會確保空氣吸入並走過全部的過濾器,過濾器就像是空氣淨化器的多層過濾各司其職,處理器負責把新鮮的空氣排出。這是一個很是很是常見的框架的設計模式,幾乎全部的面向數據流的框架,好比Web框架、Web MVC框架(ASP.NET MVC、Spring MVC等)、網絡框架(Netty、Mina等)都會採用這種模式。由於這種模式: app

  1. 由處理器來定義框架的主要的數據處理模型,由過濾器定義數據的加工模型,很是適合於基於數據處理的框架。
  2. 可讓框架有極大的擴展性,由框架來搭建主線流程,提供一個抽象模型,由開發人員來真正實現數據的處理業務邏輯。
  3. 很是符合職責分離的原則,並且很是靈活,有的框架甚至提供了運行時根據實際狀況臨時構建各類過濾器和處理器的棧。

這種模式有很是多的變種,並且過濾器和處理器之間每每在概念上也會有一些融合: 框架

  1. 框架會暴露出一些事件由過濾器來處理,有的時候這些事件僅僅是發生在處理器以前(如第一個圖)
  2. 有的時候在處理器執行前和執行後都會有一些事件,甚至過濾器是能夠改變已處理的結果的(如第二個圖)
  3. 過濾器只是訂閱了全部或部分事件進行處理的一些類,只要過濾器註冊到了框架那麼框架就會按照必定的順序去執行過濾器,每每過濾器是會有多個,處理器只會有一個,若是有多個的話也是最終選擇一個合適的處理器來處理
  4. 有框架處理器也是職責鏈式的(如第三個圖),容許多個處理器對數據或部分數據進行處理,這個時候處理器又有一點像過濾器了(也有一些框架沒有過濾器,只有處理器鏈)
  5. 有一些框架的過濾器和處理器是各自獨立的,它們只是管線的一部分,沒法感知其它過濾器或處理器
  6. 而有一些框架的過濾器能夠在過濾的過程當中直接告知框架跳事後續的過濾器,或告知框架執行哪一個處理器,甚至是告知框架跳過處理器直接提供結果
  7. 相似的,若是框架容許多個處理器,有的框架是容許處理器告知框架是否要執行後續的處理器的
  8. 大多數框架的過濾器管線在處理器以外,而有一些框架在處理器的內部安排了一組過濾器,也就是下圖的整個圖就是一個處理器,其中的處理器變爲了執行器
  9. 有的框架的內部有多條過濾處理的管線,甚至是結合了分發器一塊兒使用,咱們回想一下咱們的Web MVC框架是否是就是由分發器->處理器(處理器自己由前置過濾->處理->後置過濾+另一組前置過濾->處理->後置過濾構成)的?
  10. 有框架沒有使用過濾器這個名詞而是叫攔截器,本質上差別不大,若是攔截器和處理器並存那麼須要辨別其差別

總之,掌握了這個重要的模式不但對本身寫框架有幫助,對使用其它框架也很是有幫助。 異步

 

依賴注入

 

首先想說一個比較偏激的觀點,有不少框架對於其內部的組件採用了全面的外部依賴注入,框架總體的靈活性很高耦合很低,可是這樣就會致使框架自己不是那麼高內聚並且會形成框架使用者一些困擾,我是不太喜歡這麼作的,我框架內部的組件能夠是由依賴注入的,可是最好提供一個配置中心,提供統一的注入點而不是由使用者在用的時候來初始化這個框架的結構。因此對於框架提供的依賴注入,我更以爲它應該爲各類由框架使用者提供的代碼(好比插件、過濾器、處理器)進行依賴的解析。 分佈式

對於不少框架若是它自己就是會和業務邏輯進行綁定的,那麼提供一個依賴注入的入口可能必不可少(不是提供依賴解析的功能),咱們能夠提供相似一個IDependencyResolver/Adapter的接口,由框架的使用者去選擇合適的IOC框架或類庫進行實現,使得框架建立的外部對象都可以獲得依賴的解析。 ide

對於一些全棧框架,甚至能夠在框架內部自帶一個默認的IOC實現,使用和框架更爲契合的API爲使用者進行自動的依賴解析,在大多數時候咱們真的不須要一個複雜且完整的東西,咱們只須要一個簡單且高效的東西。 函數

 

各類器/者(Er/Or)

 

以下列出了各類ErOr,其中粗粒度的一些類型在以前的一些模式中有提到過部分,其他部分也是不少框架的老面孔了:

 

粗粒度/高抽象層的:

  • Dispatcher
  • Consumer
  • Producer
  • Publisher
  • Subscriber
  • Handler
  • Filter
  • Interceptor
  • Provider
  • Container
  • ……

 

中粒度/中抽象層的:

  • Locator
  • Creator
  • Initializer
  • Reader
  • Writer
  • Activator
  • Finder
  • Builder
  • Selector
  • Visitor
  • Loader
  • Descriptor
  • Generator
  • Adapter
  • Listener
  • Wrapper
  • Mapper
  • Binder
  • Invoker
  • Executor
  • Detector
  • Tracer
  • Decorator
  • Mapper
  • Resolver
  • Processor
  • Advisor
  • ……

 

細粒度/底層的:

  • Locker
  • Iterator
  • Extractor
  • Accessor
  • Validator
  • Formatter
  • Converter
  • Replacer
  • Comparer
  • Manager
  • Combiner
  • Parser
  • Encoder
  • Decoder
  • Importer
  • Exporter
  • Editor
  • Modifier
  • Evaluator
  • ……

這裏列的這些都是類型(類或接口)而不是方法,它們的共性就是以動詞的名詞形式來命名類型,通常不少時候動詞對應的是方法或函數是某個操做,之因此不少框架把函數往上提成了方法的緣由是:

  1. 框架的組件通常會有不少重用而且要支持組件擴展,把函數提取爲類型才能夠享受到抽象、繼承、多態。
  2. 不少高層次和中層次的這些ErOr每每對應的是一種設計模式,設計模式能讓複雜的代碼變得有調理和充實飽滿。
  3. 框架的代碼通常比較複雜,只有讓每個組件都有很明確的職責,才能讓代碼清晰。固然,若是你的Replacer真的是一句replace(),你的Encoder()真是隻是一句encode(),那是沒有必要搞這麼複雜的。若是在重構的時候咱們發現方法的邏輯有重複,而且邏輯涉及到多種實現,咱們就能夠考慮把方法提高到ErOr而後經過模版方法避免重複。

在這裏沒法一一對每一種類型進行詳細的闡述,若是你們有興趣能夠去下載一些框架的源碼,進一步學習一下每種ErOr背後的模式和用法,不少名詞都已經成爲了標準,因此若是你也採用一樣的名詞來命名相關類型的話,會助於閱讀你框架源碼的人理解。

 

發佈訂閱和消息總線

 

對於分佈式應用程序,咱們常常會採用消息總線或發佈訂閱隊列來解耦各類組件。

經過發佈訂閱,咱們能夠解耦發佈者和訂閱者:

  1. 讓關心某個數據的組件能夠訂閱某個話題,且不用關心數據的來源
  2. 讓產生某個數據的組件能夠談論某個話題,且不用關心是否誘人訂閱了個人話題
  3. 同時能夠實現不少高級功能,好比按照模式來訂閱話題,消息的持久化等等

若是你的系統中有大量的異步事件,實現了發佈訂閱模式,那麼無論未來擴展了多少事件以及事件的處理變得多少複雜,咱們都不用去改變既有的實現。

總線是一種特殊的發佈訂閱模式,經過總線,咱們解耦提供者和消費者:

  1. 讓全部服務的提供者本身註冊到總線上,告知總線提供某個服務
  2. 讓全部服務的消費者在無需知道下游提供者的狀況下進行服務的調用
  3. 同時總線能夠作不少額外的工做,好比負載、心跳、限流、短路等

若是你的系統中提供了很是多的服務,使用了總線,服務之間的調用就不會有任何具體實現的依賴。

其實我想畫一個圖的,但Visio老是把箭頭亂指,折騰了老半天箭頭永遠是斜着的,太難看了算了

你可能會問,用於分佈式應用程序的模式和咱們框架的設計有什麼關聯?我的以爲若是你從事的是一個具備狀態的框架(好比IOC就是一種具備狀態的框架,但MVC不該該是,或應用程序,特別是桌面和移動應用程序)的開發,那麼有些時候可能會使用到一下這兩種模式來進行大規模的解耦。怎麼使用?用RabbitMQ、Kafka? 不是,能夠本身嘗試實現:

  1. 一個基於內存的容器(做爲Broker),由容器來管理話題(元數據)以及上下游對象,而且進行消息的路由(轉發),這就實現了發佈訂閱
  2. 若是要實現總線,在這個基礎上擴展一下便可,能夠想到若是有人訂閱Request且發佈Response那麼它是服務的提供者(不過Request只用推送給任意一個訂閱者便可),若是有人發佈Request且訂閱Response那麼它是服務的消費者
  3. 固然,做爲框架內使用的總線,咱們可能不須要去實現諸如負載、心跳、限流等功能,數據的流轉也能夠是同步模式的

放到最後說這個模式是由於這實際上是一個不錯的練習,若是你有興趣的話看了本文能夠本身去嘗試使用本文介紹的一些步驟和模式實現一個基於內存的輕量級的RabbitMQ/Kafka做爲練習,謝謝閱讀。另外推薦你們能夠去看一下POSA面向模式的軟件架構的前兩卷。

相關文章
相關標籤/搜索