被濫用的 GUI 設計模式

原文來自 被濫用的 GUI 設計模式編程

隨便侃些我的對 GUI 設計模式的見解。設計模式

近些年來,隨着 Fronted 技術的火熱和推動,古老的(至少有幾十年歷史)用來解決 GUI 應用中代碼組織問題的「GUI 設計模式」如今也成爲了 Frontend 工程師的熱門話題,MVC、MVP、MVVN 等設計模式在網路上被議論不絕。有不少工程師開始經過寫博文來介紹它們、闡述本身對它們的理解,甚至在 Github 上開源了各類 GUI 設計模式的實現。網絡

順着這種趨勢,不少 Frontend 工程師甚至把 GUI 設計模式當成一種「規範」乃至「教條」。然而糟糕的現實是,大多數人並無正確地、細緻地理解和運用 GUI 設計模式,反而由於 Tradeoff 致使它的缺點被放大。結果就是你用了大量精力、模板代碼去設計它,反而讓它更復雜、更難維護了。函數式編程

例如,當你打開 Github 上大多數試圖實現 GUI 模式的倉庫時會發現,整個應用大概也就兩三個頁面、四五個網絡接口,就可能已經建立了幾十個類和接口來承載那單薄的邏輯了。舉個更具體的例子,我我的曾經接觸過幾個用 MVP 模式設計的大型 Android 工程,在進行維護或者迭代的時候,各類帶有問題的設計反而讓 MVP 模式成爲了累贅。函數

首先,工程中大多數 View 都是粒度大耦合度高的 Activity 類,並且不少 View 裏爲了方便,會提供 fun updateView(user: UserModel) 這樣的方法,致使 View 和領域/業務模型直接耦合了。再者,View 和 Model 中還會包含了跳轉頁面、發送全局消息等各類帶有「反作用」的命令,這也讓面向接口編程成爲了形式主義。工具

因此與其「捨本逐末」、「知其然而不知其因此然」,倒還不如理解問題的本質。於 GUI 設計模式而言,實際上最重要的思想是「分而治之」,經過把以前都寫在一處的代碼按照職能分到不一樣的類,來讓它們實現「低耦合高內聚」。因此,咱們更應該把 GUI 設計模式當成一思想而不是具體的手段,更也不必用各類所謂的模板來解決問題,只要你能把熱點、關鍵代碼設計得足夠低耦合高內聚,那麼你徹底能夠無視全部 GUI 設計模式。post

例如上面提到的 fun updateView(user: UserModel) 問題,實際有兩種方式來讓 View 和業務模型 UserModel 解耦:學習

// 方法一
interface ViewA {
    fun updateText1(text: String)
    fun updateText2(text: String)
    // ...
}
class PresenterA {
    fun onSomeEvent() {
        val userModel = Apis.requestUser()
        viewA.updateText1(userModel.name)
        viewA.updateText2(userModel.age.toString())
    }
}

// 方法二
interface ViewA {
    data class ViewAttributes(
        text1: String,
        text2: String,
        // ...
    )
    fun updateView(view: ViewAttributes)
}
class PresenterA {
    fun onSomeEvent() {
        val viewAttributes = Apis.requestUser().mapTo ViewAttributes()
        viewA.updateView(viewAttributes)
    }
}
複製代碼

方法一更傾向於用「指令」來描述 View,方法二則更傾向於用「數據」。而我我的更喜歡方法二,由於數據是運行時可處理、可持久化的,甚至能夠跨進程、跨語言、乃至跨機器共享的。講個題外話,Web Fronted 裏 Redux 等狀態管理工具捧起了一個很火的詞「時間旅行」。在我看來核心思想其實也是把指令下沉,用數據(/狀態)來描述上層邏輯,這樣就能夠在運行時實現邏輯可記錄、可回放。ui

這裏還有一點須要注意的,ViewAttributes 必須是 View 的領域模型,字段名稱應當僅和 View 自己相關,而不該該和其餘領域有關係。spa

再回到以前提到過的另一個問題:View 和 Model 裏的反作用。這個其實更容易解決,只須要把全部反作用移到外部(/調用方)就行了。例如:

// 有反作用
class ViewA {
    fun onTitleClick() {
        sendBroadcast("x")
    }
}

// 無反作用
class ViewA {
    fun onTitleClick() {
        caller.onTitleClick()
    }
}
class PresenterA {
    fun onTitleClick() {
        sendBroadcast("x")
    }
}
複製代碼

實際上,只懂得 OOP(面向對象編程)的工程師很容易形成前面提到的問題,由於他們習慣了依賴「外部狀態」來解決問題(類的實例自己也是一個狀態),可是在狀態數量不斷增長的狀況下,狀態的管理反而會成爲一個新的大難題。而 OOP 提倡類的「低耦合高內聚」實際上能夠當作是在解決狀態管理的問題。

因此在文章的最後,我強烈推薦工程師們能夠學習下 FP(函數式編程)。相對於 OOP 而言,FP 的思想則是摒棄外部狀態,它實現的是粒度更小的函數級別的「低耦合高內聚」,你只須要保證你的函數是無反作用的而後管理好函數內部的狀態就能夠了。而維持這種編程思想,能讓你輕鬆駕馭巨型、複雜的項目,甚至能讓你的代碼更容易被調試,更容易被並行執行。

對於 Android 工程師們來講,Kotlin 目前的火熱正是讓你們有了更瞭解 FP 的機會。以後我也會寫些和 Kotlin、FP 有關的文章。

相關文章
相關標籤/搜索