學(xué)習(xí)使用C#語言構(gòu)建MVC架構(gòu)的筆記

前置知識(來源于網(wǎng)絡(luò)):
關(guān)于MVC:MVC 模式代表 Model-View-Controller(模型-視圖-控制器) 模式。這種模式用于應(yīng)用程序的分層開發(fā)。
Model(模型) - 模型代表一個存取數(shù)據(jù)的對象;
View(視圖) - 視圖代表模型包含的數(shù)據(jù)的可視化。
Controller(控制器) - 控制器作用于模型和視圖上。它控制數(shù)據(jù)流向模型對象,并在數(shù)據(jù)變化時更新視圖。它使視圖與模型分離開。
MVC的歷史:MVC 模式由 Trygve Reenskaug 在 1978 年提出[,是施樂帕羅奧多研究中心(Xerox PARC)在 20 世紀(jì) 80 年代為程序語言Smalltalk 發(fā)明的一種軟件設(shè)計模式。
MVC模式的作用:MVC 模式的目的是實現(xiàn)一種動態(tài)的程序設(shè)計,使后續(xù)對程序的修改和擴展簡化,并且使程序某一部分的重復(fù)利用成為可能。
MVC模式的改良:
MVP模式:在上個世紀(jì)90年代,IBM旗下的子公司Taligent在用C/C++開發(fā)一個叫CommonPoint的圖形界面應(yīng)用系統(tǒng)的時候提出來的。
MVVM模式:MVVM模式最早是微軟公司提出,并且了大量使用在.NET的WPF和Sliverlight中。2005年微軟工程師John GosSman在自己的博客上首次公布了MVVM模式。
三層架構(gòu):三層架構(gòu)是一種程序設(shè)計結(jié)構(gòu),使用分層式結(jié)構(gòu)將整個應(yīng)用拆分為三層:最上層:表示層。中間層:業(yè)務(wù)邏輯層。最底層:數(shù)據(jù)訪問層。三層都依賴于實體類,因為數(shù)據(jù)傳輸通過實體類來傳輸。上層依賴于下層,下層不依賴于上層,但下層需要向上層返回結(jié)果。
正文:
一般的程序采用類似逐過程的開發(fā)方式,即“產(chǎn)生需求——構(gòu)造邏輯——添加組件——改善邏輯——添加組件...”,最終結(jié)果是產(chǎn)出一坨意大利面條式代碼,流程混亂難以維護和復(fù)用。一次偶然的機會我在網(wǎng)上看到了關(guān)于MVC模式的介紹,看完后覺得應(yīng)該嘗試一下,但是苦于網(wǎng)絡(luò)上缺乏C#及Godot引擎的MVC模式代碼,于是自己動手實現(xiàn)(姑且算是)了一個MVC模式。
參考網(wǎng)上的資料,MVC模式的核心在于UI、邏輯、數(shù)據(jù)的解耦,為了達(dá)成三方的解耦,我選擇了單例模式及接口作為模塊間引用的方式,以后有機會的話再考慮MVVM使用XAML雙向綁定的方法。
這次的例子是Godot引擎,需求是“按下按鍵后角色移動”,這個需求應(yīng)該是游戲開發(fā)最常見的需求之一了,實現(xiàn)起來也比較簡單。
首先是搭建場景,創(chuàng)建一個Player節(jié)點(Godot里叫Node,等于Unity里的GameObject)

在Player節(jié)點上掛載PlayerView腳本。
在這里也展示一下MVC模式的腳本結(jié)構(gòu):

PlayerView.cs是Player的View層,負(fù)責(zé)接受按鍵事件并返回。
Move.cs是Player的Controller層,負(fù)責(zé)邏輯計算。為了防止頻繁觸發(fā)new產(chǎn)生內(nèi)存垃圾,這里使用了單例模式。
PlayerEntity.cs是Player的Model層,負(fù)責(zé)構(gòu)建Player的數(shù)據(jù)模型。因為Player對象全局唯一,這里使用了單例模式。(目前沒有實現(xiàn)數(shù)據(jù)庫,所以現(xiàn)在數(shù)據(jù)直接放在Model層內(nèi))
Iname.cs, ISpeed.cs, IPlayer.cs是Model接口,Model層實現(xiàn)Model接口,Controller層調(diào)用Model接口間接訪問Model層數(shù)據(jù)。
IPosition.cs比較特殊,它是PlayerView的接口。因為PlayerView既是Player的View層,它也控制Player節(jié)點的數(shù)據(jù),所以從后一個角度看它也可以算是一個特殊的Model,也需要實現(xiàn)一個接口來傳遞節(jié)點的數(shù)據(jù)。
具體實現(xiàn)代碼:
觀察代碼,按鍵事件產(chǎn)生后先由PlayerView.cs接收事件,在內(nèi)部判斷具體是按下了哪個按鍵,并根據(jù)按鍵是W|S|A|D分別調(diào)用Move.cs的MovingUp|MovingDown|MovingLeft|MovingRight方法,并傳入實現(xiàn)ISpeed接口的PlayerEntity單例與實現(xiàn)IPosition接口的自身;Move.cs的方法接入傳參后通過ISpeed獲取PlayerEntity的Speed、通過IPosition獲取PlayerView的Position,進(jìn)行運算后再通過IPosition向游戲引擎返回修改后的Position。
架構(gòu)優(yōu)點:方便擴展修改:在寫這篇筆記的過程中代碼還優(yōu)化了好幾次,我自己感覺不管是修改API還是擴展API都比之前的面條式代碼方便許多,例如在寫文章之前MovingUp/Down/Left/Right之前是Moving,Moving方向的判斷放在了Controller層,后來我把方向判斷的邏輯放在了View層也并不影響PlayerView、PlayerEntity的代碼。
架構(gòu)缺點:
1.資料難找:雖然MVC架構(gòu)是 20 世紀(jì) 80 年代提出的,但是直到現(xiàn)在要找一個簡單清晰的MVC代碼例子仍然不方便,尤其是C#語言(Java圈對架構(gòu)、設(shè)計模式的熱衷讓人羨慕)。
2.腳本數(shù)量多:為了實現(xiàn)按鍵移動,在例子里創(chuàng)建了6個腳本(IName.cs其實并非必須文件,它存在的意義是體現(xiàn)C#接口可通過接口的多重繼承組合成新的接口來服務(wù)Model層不同的業(yè)務(wù)需求),程序需求增加的情況下,腳本文件數(shù)量的增長可能會超過預(yù)期。
3.邏輯混亂:沒錯,是邏輯混亂,雖然MVC將數(shù)據(jù)與邏輯分離,但也產(chǎn)生了一個問題:“邏輯是放在Controller層還是放在View層?”在寫這篇文章之前,這個例子里也出現(xiàn)了把捕獲按鍵事件和判斷具體按鍵分開的情況,最后我將兩個邏輯合并到View層中,但也多少違背了“Controller層負(fù)責(zé)邏輯”的原則,后面可能會分離出一個類似“InputEventKeyController”這樣的Controller類,但那也是后話了。
總結(jié):程序開發(fā)沒有銀彈,有的是對“分離變與不變”原則的各種實踐。缺乏設(shè)計與過度設(shè)計都不可取,只有在具體的開發(fā)環(huán)境中才能體會到原則的可貴與實踐的困難。