MVC之患上肥胖症的Controller

來這邊負責做的一個項目,用了一個叫做 Yii Framework 的 MVC 框架,剛開始的時候自以為結構很穩健(當然,是相對於現有的爛系統來講)。但是隨着對業務邏輯理解的深入,我開始意識到問題的嚴重,我錯誤地理解了 MVC 中的 Controller,想當然地根據以往的經驗,把所有的業務邏輯都放在 Controller 的 action 中去實現,於是,每一個 Controller 的代碼都上千行,越來越臃腫。

最終導致我下定決心對這兩個月工作進行重構的,是一個對外開放 API 接口的需求。按照現在的架構,代碼基本無法復用,我需要把很多功能再重複寫一遍,這實在是無法接受的。面向對象編程不僅僅是課本上的名詞啊!真正開始實踐才發現要有面向對象意識,有全局觀,是多麼難得的一件事情。

一、到底什麼是 MVC?

模型-視圖-控制器(MVC)是一種設計模式。

MVC 的目標是將業務邏輯從用戶界面的考慮中分離,這樣開發者就可以更容易地改變每一部分而不會影響其他。在 MVC 中,Model 代表信息(數據)和業務規則;View 包含了用戶界面元素,例如文本,表單等;Controller 則管理模型和視圖中的通信。

MVC 在各種編程語言中均有實現,例如 J2EE 應用開發中,View 可能由 jsp 實現;Controller 是一個 servlet,現在一般用 Struts 實現;Model 則是由一個實體 Bean 來實現。

二、我遇到了什麼問題?

Yii Framework 是一個流行的 PHP 框架,它借鑒了 Ruby on Rails 的 ActiveRecord(AR) 概念,數據庫中的每一個 table 都可以用 AR 類來方便地進行增刪改查操作,它把 AR 當做 Model,並推薦放在一個名為 models 的目錄下面。於是,我在自動生成表對應的 AR 之後,便望文生義想當然地認為已經擁有了 Model 層。其實,AR只不過是 DAO (數據訪問層),並不是 Model 層。

我們的業務幾乎全放在了 Controller 里:對用戶提交上來的表單進行各種邏輯判斷,進行計算,實例化 AR 對數據進行存儲…… 因為一個 Controller 中會有多個 action,每個 action 都有這樣的業務處理,最後,我發現我的 Controller 代碼已經超過了 1000 行。

突然有一天,leader 說我們這個系統要開放 API 給現有的舊系統調用,要給第三方接口。第三方只是要給定一個參數,本系統給出個結果值而已,這其中的業務處理它是不關心的。壞就壞在這裡,Controller 已經實現了那些業務,但它是接受表單提交的,怎樣能夠也接受 SOAP 的 xml 文檔呢?

Controller 和套套一樣,應該越薄越好。它的職責應該只是接受用戶的輸入,然後立刻轉發給別的類來處理。這樣 Controller 只負責提供不同的接口,我們才能算是將業務邏輯分離出去,而分離出去的業務也很容易進行重用。分離出來的這部分業務由誰來處理呢?答案應該是 Model。

三、我是怎麼解決的?

不要片面地理解了 Model,它不僅僅只是對數據表進行增刪改查。是的,AR 是最自然的基於 DB table 的 model。對大多數應用而言,使用 AR 就足夠了。但對於複雜分層還要開放接口的場景,我們需要考慮在 AR 基礎上進一步寫各種 model,封裝基於各種領域的業務邏輯。

為了給 Controller 減肥,有很多重構工作需要做。我為每一個 Controller 抽象出了一個 Utility 類,每當需要進行業務處理時就實例化調用這個類,同時 API 接口部分也會使用到這個類,從而保證了面向對象代碼復用。

重構過程中遇到很多設計模式方面的問題,這篇文章給出了多參數方法的重構方案,受益匪淺。

Auer 曾在文獻【AUER95】中指出重構時應當遵守的原則:

  1. 應當根據行為而不是狀態定義一個類。一個類的實現首先建立在行為的基礎之上,而不是建立在狀態的基礎之上。
  2. 在實現行為時,是用抽象狀態而不是用具體狀態。如果一個行為涉及到對象的狀態時,使用間接的引用而不是直接的引用。換言之,應當使用取值方法而不是直接引用屬性。
  3. 給操作劃分層次。一個類的行為應當放到一個小組核心方法(Kernel Methods)裡面,這些方法可以很方便地在子類中加以置換。
  4. 將狀態屬性的確認推遲到子類中。不要在抽象類中過早地聲明屬性變量,應將它們盡量地推遲到子類中去聲明。在抽象超類中,如果需要狀態屬性的話,可以調用抽象的取值方法,而將抽象的取值方法的實現放到具體子類中。

四、得到的啟示

Yii Framework 的官方文檔中有這麼一段——

In a well-designed MVC application, controllers are often very thin, containing probably only a few dozen lines of code; while models are very fat, containing most of the code responsible for representing and manipulating the data.

簡言之,Rich Model is Better

參考鏈接:
我們丟失了 Model 層
MVC演化史