1 引言
隨著前端 ES6 ES7 的一路前行, 我們大前端借鑑和引進了各種其他程式語言中的概念、特性、模式; 我們可以使用函式式 Functional 程式設計設計,可以使用面向物件 OOP 的設計,可以使用面向介面的思想,也可以使用 AOP, 可以使用註解,代理、反射,各種設計模式; 在大前端輝煌發展、在資料時代的當下 我們一起閱讀了一篇設計相關的老文: 《The DCI Architecture》 一起來再探索和複習一下 相關的設計和思想
2 內容摘要DCI 是資料 Data 場景 Context 互動 Interactions 簡稱, 重點是關注 資料的不同場景的互動行為, 面向物件的系統 狀態和行為的一種正規化設計; DCI 在許多方面是許多過去正規化的統一,多年來這些模式已經成為面向物件程式設計的輔助工具。
儘管面向切面的程式設計(AOP)也有其他用途,但 DCI 滿足了許多 AOP 的應用以及 Aspects 在解決問題方面的許多目標。根據 AOP 的基本原理,DCI 基於深層次的反射或超程式設計。 與 Aspects 不同,角色聚合並組合得很好。Context 提供角色集之間的關聯的範圍關閉,而 Aspect 僅與應用它們的物件配對。 在許多時候,雖然混合本身缺乏我們在 Context 語義中發現的動力 ,但 DCI 反映了混合風格策略。 DCI 實現了多正規化設計的許多簡單目標,能夠將過程邏輯與物件邏輯分開。然而,DCI 具有比多正規化設計提供的更強大的技術更好地耦合和內聚效果
結合 ATM 匯款場景案例,講解了一下 DCI 角色提供了和使用者相關 自然的邊界,以轉賬為例,我們實際談論的是錢的轉移,以及源賬戶和目標賬戶的角色,演算法(用例 角色行為集合)應該是這樣: 1.賬戶擁有人選擇從一個賬戶到另外一個賬戶的鈔票轉移。 2.系統顯示有效賬戶 3.使用者選擇源賬戶 4.系統顯示存在的有效賬戶 5.賬戶擁有人選擇目標賬戶。 6.系統需要數額 7.賬戶擁有人輸入數額 8.鈔票轉移 賬戶進行中(確認金額 修改賬戶等操作)
設計者的工作就是把這個用例轉化為類似交易的演算法,如下: 1.源賬戶開始交易事務 2.源賬戶確認餘額可用 3.源賬戶減少其帳目 4.源賬戶請求目標賬戶增加其帳目 5.源賬戶請求目標賬戶更新其日誌 log 6.源賬戶結束交易事務 7.源賬戶顯示給賬戶擁有人轉賬成功。
template <class ConcreteAccountType>class TransferMoneySourceAccount: public MoneySource{private: ConcreteDerived *const self() { return static_cast<ConcreteDerived*>(this); } void transferTo(Currency amount) { // This code is reviewable and // meaningfully testable with stubs! beginTransaction(); if (self()->availableBalance() < amount) { endTransaction(); throw InsufficientFunds(); } else { self()->decreaseBalance(amount); recipient()->increaseBalance (amount); self()->updateLog("Transfer Out", DateTime(), amount); recipient()->updateLog("Transfer In", DateTime(), amount); } gui->displayScreen(SUCCESS_DEPOSIT_SCREEN); endTransaction(); }
嘗試從人類思維角度出發 理解DCI 即 資料(data) 場景(context) 互動(interactive)。
DCI 之所以被提出,是因為傳統 mvc 程式碼,在越來越豐富的互動需求中變得越來越難讀。有人會覺得,複雜的需求 mvc 也可以 cover 住,誠然如此,但很少有人能只讀一遍原始碼就能理解程式處理了哪些事情,這是因為人類思維與 mvc 的傳統程式設計思想存在鴻溝,我們需要腦補內容很多,才會覺得難度。
現在仍有大量程式使用面向物件的思想表達互動行為,當我們把所有物件之間的關聯記錄在腦海中時,可能物件之間互動行為會比較清楚,但仍無法輕鬆理解,因為物件的封裝會導致內聚性不斷增加,互動邏輯會在不同物件之間跳轉,物件之間的巢狀關係在複雜系統中無疑是一個理解負擔。
DCI 嘗試從人類思維角度出發,舉一個例子:為什麼在看電影時會輕輕鬆鬆的理解故事主線呢?回想一下我們看電影的過程,看到一個畫面時,我們會思考三件事:
畫面裡有什麼人或物?人或物發生了什麼行為、互動?現在在哪?廚房?太空艙?或者原始森林?很快把這三件事弄清楚,我們就能快速理解當前場景的邏輯,並且輕鬆理解該場景繼續發生的狀況,即便是盜夢空間這種燒腦的電影,當我們搞清楚這三個問題後,就算街道發生了 180 度扭曲,也不會存在理解障礙,反而可以吃著爆米花享受,直到切換到下一個場景為止。
當我們把街道扭曲 180 度的能力放在街道物件上時,理解就變得複雜了:這個函式什麼時候被呼叫?為什麼不好好承載車輛而自己發生扭曲?這就像電影開始時,把電影裡播放的所有關於街道的狀態都走馬燈過一遍:我們看到街道通過了車輛、又捲曲、又發生了爆炸,實在覺得莫名其妙。
理解程式碼也是如此,當互動行為複雜時,把互動和場景分別抽象出來,以場景為切入點互動資料。
舉個例子,傳統的 mvc 可能會這麼組織程式碼:
UserModel:
class My { private name = "ascoders" // 名字 private skills = ["javascript", "nodejs", "切圖"] // 技能 private hp = 100 // 生命值?? private account = new Account() // 賬戶相關}
UserController:
class Controller { private my = new My() private account = new Account() private accountController = new AccountController() public cook() { // 做飯 } public coding() { // 寫程式碼 } public fireball() { // 搓火球術。。? } public underAttack() { // 受到攻擊?? } public pay() { // 支付,用到了 account 與 accountController }}
這只是我自己的行為,當我這個物件,與文章物件、付款行為發生聯動時,就發生了各種各樣的跳轉。到目前為止我還不是非常排斥這種做法,畢竟這樣是非常主流的,前端資料管理中,不論是 redux,還是 mobx,都類似 MVC。
不論如何,嘗試一下 DCI 的思路吧,看看是否會像看電影一樣輕鬆的理解程式碼:
以上面向物件思想主要表達了 4 個場景,家庭、工作、夢境、購物:
home.scene.scalawork.scene.scaladream.scene.scalabuy.scene.scala以程式設計師工作為例,在工作場景下,寫程式碼可以填充我們的錢包,那麼我們看到一個程式設計師的錢包:
codingWallet.scala:
case class CodingWallet(name: String, var balance: Int) { def coding(line: Int) { balance += line * 1 }}
寫一行程式碼可以賺 1 塊錢,它不需要知道在哪個場景被使用,程式設計師的錢包只要關注把程式碼變成錢。
互動是基於場景的,所以互動屬於場景,寫程式碼賺錢的互動,放在工作場景中:
work.scene.scala:
object MoneyTransferApp extends App { @context class MoneyTransfer(wallet: CodingWallet, time: int) { // 在這個場景中,工作 1 小時,可以寫 100 行程式碼 // 開始工作! wallet.working role wallet { def working() { wallet.coding(time) } } } // 錢包預設有 3000 元 val wallet = CodingWallet("wallet", 3000) // 初始化工作場景,工作了 1 小時 new MoneyTransfer(wallet, 1) // 此時錢包一共擁有 3100 元 println(wallet.balance)}
小結:,就是把資料與互動分開,額外增加了場景,互動屬於場景,獲取資料進行互動。原文的這張圖描述了 DCI 與 MVC 之間的關係:
發現並梳理現代前端模式和概念的蛛絲馬跡現代前端受益於低門檻和開放,伴隨 OO 和各種 MV* 盛行,也出現了越來越多的概念、模式和實踐。而 DCI 作為 MVC 的補充,試圖透過引入函數語言程式設計的一些概念,來平衡 OO 、資料結構和演算法模型。值得我們津津樂道的如 Mixins、Multiple dispatch、 依賴注入(DI)、Multi-paradigm design、面向切面程式設計(AOP)都是不錯的。如果對這些感興趣,深挖下 AngularJS 在這方面的實踐會有不少收穫。 當然,也有另闢途徑的,如 Flux 則採用了 DDD/CQRS 架構。
軟體架構設計,是一個很大的話題,也是值得每位工程師長期實踐和思考的內容。個人的幾點體會:
一個架構,往往強調職責分離,透過分層和依賴原則,來解決程式內、程式間的相互通訊問題;知道最好的幾種可能的架構,可以輕鬆地建立一個適合的最佳化方案;最後,必須要記住,程式是必須遵循的架構。分享些架構相關的文章:
Comparison of Architecture presentation patterns MVP(SC),MVP(PV),PM,MVVM and MVCThe DCI Architecture: A New Vision of Object-Oriented Programming乾淨的架構 The Clean ArchitectureMVC 的替代方案展示模式架構比較 MVP(SC),MVP(PV),PM,MVVM 和 MVCSoftware Architecture Design【譯】什麼是 Flux 架構?(兼談 DDD 和 CQRS)結合 DCI 設想開發的過程中使用到一些設計方法和原則我們在開發的過程中多多少少都會使用到一些設計方法和原則 DCI 重點是關注 資料的不同場景的互動行為, 是面向物件系統 狀態和行為的一種正規化設計;
它能夠將過程邏輯與物件邏輯分開,是一種典型的行為模式設計; 很好的點是 它根據 AOP 的基本原理,DCI 提出基於 AOP 深層次的超程式設計(可以理解成面向介面程式設計), 去促使系統的內聚效果和降低耦合度;
舉個例子: 在一個 BI 系統中, 在業務的發展中, 這個系統使用到了多套的 底層圖表庫,比如: Echarts, G2,Recharts, FusionChart; 等等;
那麼問題來了,
如何去同時支援 這些底層庫, 並且達到很容易切換的一個效果?如何去面向未來的考慮 將來接入更多型別的圖表?如何去考慮擴充套件業務 對圖表的日益增強的業務功能(如: 行列轉換、智慧格式化 等等)帶著這些問題, 我們再來看下 DCI 給我們的啟示, 我們來試試看相應的解法:
圖表的模型資料就是 資料 Data , 我們可以把[日益增強的業務功能] 認為是各個場景互動 Interactions;接入更多型別的圖表怎麼搞?不同型別的圖表其實是圖表資料模型的轉換,我們也可以把這些轉換的行為過程作為一個個的切片(Aspect),每個切片都是獨立的, 松耦合的 ; 接入多套底層庫怎麼搞? 每個圖形庫的 build 方法,render 方法 , resize 方法,repaint 方法 都不一樣 ,怎麼搞 ? 我們可以使用 DCI 提到的超程式設計- 我們在這裡理解為面向介面的程式設計, 我們分裝一層 統一的介面; 利用面向介面的父類引用指向子類物件 我們就可以很方便地 接入更多的 implement 接入更多的圖形庫(當然,一個系統統一一套是最好的);4 總結DCI 是資料 Data 場景 Context 互動 Interactions 的簡稱,DCI 是一種特別關注行為的設計模式(行為模式), DCI 關注資料不同場景的互動行為, 是面向物件 狀態和行為的一種正規化設計;DCI 嘗試從人類思維,過程化設計一些行為; DCI 也會使用一些面向切面和介面程式設計的設計思想去達到高內聚低耦合的目標。