每一個可以努力的日子,都是一份厚禮。
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】中指出重構時應當遵守的原則:
- 應當根據行為而不是狀態定義一個類。一個類的實現首先建立在行為的基礎之上,而不是建立在狀態的基礎之上。
- 在實現行為時,是用抽象狀態而不是用具體狀態。如果一個行為涉及到對象的狀態時,使用間接的引用而不是直接的引用。換言之,應當使用取值方法而不是直接引用屬性。
- 給操作劃分層次。一個類的行為應當放到一個小組核心方法(Kernel Methods)裡面,這些方法可以很方便地在子類中加以置換。
- 將狀態屬性的確認推遲到子類中。不要在抽象類中過早地聲明屬性變量,應將它們盡量地推遲到子類中去聲明。在抽象超類中,如果需要狀態屬性的話,可以調用抽象的取值方法,而將抽象的取值方法的實現放到具體子類中。
四、得到的啟示
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演化史
這篇文章由lovelucy於2012-01-11 00:07發表在編程。你可以訂閱RSS 2.0 也可以發表評論或引用到你的網站。除特殊說明外文章均為本人原創,並遵從署名-非商業性使用-相同方式共享創作協議,轉載或使用請註明作者和來源,尊重知識分享。 |
Google Chrome 27.0.1453.110 Windows 7 大約11年前
“我為每一個 Controller 抽象出了一個 Utility 類,每當需要進行業務處理時就實例化調用這個類,同時 API 接口部分也會使用到這個類,從而保證了面向對象代碼復用”
能具體說下怎麼做的嗎?我也面臨相似的困惑。謝謝!
Mozilla Firefox 18.0 Windows 7 大約11年前
Module/
—Product/
—–model
——-admin.php
——-index.php
—–Index.php
—–Admin.php
Netscape Navigator 5.0 iPhone iPhone OS 5_0_1 like Mac OS X) AppleWebKit 大約12年前
贊~~~ Controller 和套套一樣,應該越薄越好。它的職責應該只是接受用戶的輸入,然後立刻轉發給別的類來處理。
Google Chrome 16.0.912.75 Windows 7 大約12年前
龍總大駕光臨啊,歡迎指導~~ 😈
Netscape Navigator 5.0 iPad OS 4_3_3 like Mac OS X 大約12年前
贊同,雖然用的是java,其實可以把許多validation放在client side,需要數據訪問的在server side, 有關model的,我覺得oracle adf的 data control和binding設計的很好,盡量多抽象一個層次的Object出來,然後除非需要transaction,才提交數據庫
Google Chrome 16.0.912.75 Windows 7 大約12年前
validation在後端也必須要有,否則不懷好意者偽造數據進行提交會有安全問題。瀏覽器前端驗證只是為了展現更友好的用戶體驗。
Google Chrome 18.0.1017.2 Windows 7 大約12年前
與數據提交的validation放後端,類似長度,非空等等不用依賴於後台的validation可以放前台,可以適當的減少數據傳輸,提高響應~至於用戶體驗,那還是依賴於js。