model

MVC,令人搞不懂的Model。

自學web開發兩年,對MVC一直一知半解,常在想怎樣設計系統會最漂亮。整理了一下目前的理解,以Model出發跟各位分享。其中有寫錯的部份也期待各位前輩指教。

Q1: 一個model對應到資料庫一張table對嗎?

不對。雖然大部分framework實作都會將一張table對應到model內一個class,但「一個model對應一張table」比較像是在描述ORM之類幫助簡化資料庫SQL操作的class,而model是「處理跟某張table相關任務的class」。

差別何在?一個model可能會處理到多張table的資料,也可能一張table都不碰,純粹處理一串商業邏輯。舉例來說,可能會有個負責整理行銷資料的model叫Marketing,從多張table撈資料出來,整理之後丟給controller。而資料庫中可能有,也可能沒有「marketing」這張table。

Q2: 可是我寫專案幾乎一個model對應到一張table啊?

因為實務上一個服務常會對應到一個model,而提供這model所需的資料常常就只是一張table。所以許多情況下的確是一張model對應到一張table沒錯。

但也不能說是「一張」table。model內常會去join其他相關的table對吧?所以說「model對應到一張table」或是「model就是一種ORM」都是不精確而誤導的說法。雖然寫起來常會有這種錯覺。

Q3: 那你用一個不會誤導又精確的說法描述model給我看?

model是layer」。不要把MVC中的M跟VC想成平行的分工關係,把VC跟M想成上往下的幾層layer。VC最靠近使用者、下來是M、再下來是database。所以VC要取得資料庫的資料都要透過model,而不同任務有不同model。

Q4: 聽不太懂,談點別的好了。從Rails社群流出一句很流行的MVC原則「fat model, skinny controller」,你對此有什麼想法?

這句話對初學者有幫助,對進階者有害。「fat model, skinny controller」這句話可以提醒初學者盡量把code丟進model別丟進controller。你再看一次我Q3的回答,一個應用程式的最重要部份(商業邏輯、安全性、效能)本來就是跟資料庫互動的那層layer,也就是model,所以把code丟進model別丟進controller可以暗示初學者寫出reusable的code。

但是把太多code寫進model會形成god pattern,也就是某個model跟神一樣負責絕大部分的任務,導致維護起來有困難(想像你初學PHP把全部code寫在同一份檔案的恐怖情況)。

Q5: 又說code要盡量丟進model,又說model內code太多很不好,不然code到底要丟哪?

library以及helper。現在大部分的framework都預設有放library以及helper的地方。不使用framework的話你也可以自己開這兩個資料夾。

多個model常做的同幾件事可以包起來寫成library,而單純的資料格式轉換(甚至是產生相關html/css)之類的任務可以寫成helper。讓model主要專注在商業邏輯(這是最花功夫的地方),其餘的搬到library、helper、或是有些framework稱之為third_party或package的什麼地方都好。別丟進controller!更別丟進view。

Q6: 好吧。可是我開發時在controller跟model內真的會不小心就寫掉大部分任務,這樣寫真的比較快阿。你開發時,上面說的你自己有沒有都做到?

沒有。原因有兩個。一,實務上的確很難在一開始就把架構想清楚,所以一堆任務會丟給model。二,常常越往下寫才發現某些任務是共通的、某些任務一開始只是「簡單」處理資料轉換,寫到後來變成「複雜的」處理資料轉換,我認為這時再把它拉出來成為library或helper即可。重構本來就是不斷在做的事情,時機到了就做*(註1)。

Q7: 我知道library跟helper的地位何在了。但,library跟helper的差別又何在?

軟體開發時,有時會寫幾個function或method解決某些任務,有時候覺得這樣太鬆散,會包成class。library常常是class、helper常常是一組function。以我自己來說,常在controller或view使用helper,在model使用library。

Q8: 關於model,最後有沒有要提醒的?

不要在model裡面用session variable。用下去,那model根本不再reusable(其他controller用之前還要先設定好相關session)。連unit testing都毀了。解決之道是dependency injection。session variable某種程度來說根本就是global variable,濫用global variable是很不好的。

註1:這段話可能很有問題。我在startup工作,開發上極度注重「速度」,常為了拼上線時間而捨棄其他事情。

(Photo via  Alaskan Dude, CC License)

  • http://frost.tw 蒼時弦や

    http://en.wikipedia.org/wiki/Single_Table_Inheritance

    我覺得用多個 Model 共用一個 Table 好像會比較接近實用的案例。
    (Rails 裡面先產生一個 Main Model 之後其他 Model 繼承,而 Table 中有 type column 就會自動產生 STI 效果)

    • 尤川豪

      非常謝謝你 原來有STI這種pattern
      我竟然到今天才知道 Q_Q

      • http://frost.tw 蒼時弦や

        可以看看 ihower 大大的「Rails 實戰聖經」印象中裡面有提到 XD

      • Blueheart

        STI在Java ORM世界是很常見的東西,但是我看過的幾個大專案裡面,完全不需要用到這種進階的Mapping.

        我自己的專案裡面是有用過STI, 不過是為了達到某些特殊功能。但個人是覺得這樣的做法很不直覺.

        大多都是單一Table對單一Model, Model跟Model之間的關係是透過Relationship定義 (即OneToMany, OneToOne…之類的)

  • Blueheart

    建議你可以去了解一下這幾個東西
    1. Active Record Pattern
    2. Service and Repository Pattern
    3. Layered architecture

    • 尤川豪

      感謝指點方向