在開始之前,我們先來討論一下,上一篇最後讓大家想的問題:
下面是我自己對這幾題的想法:
其實會問上面這些問題,我只是想讓你知道「Everything is optional」,當你在做一個專案的時候,所有的 tech stack 你都是可以自由選擇的。不是因為別人都用 React 所以你用 React,而是因為「你有必要」用 React,或是「用了 React 會比較好」你才去選擇它。
最近剛做完公司的一個小網頁,我就用 jQuery + ES5 語法去寫它,甚至連 js 檔案都沒獨立出來,直接寫在 裡面。為什麼要這樣?因為就只有那一個頁面而已,我沒必要去串整個 framework。會很多工具很好,但你必須知道你為什麼選擇那一項工具。記住了,所有的工具都是可以選擇的,你不一定要全部一起用。你也可以只用 React 不用 Redux,只用 Express 不用 template engine。
好,那我們繼續開始今天要講的主題:MV*,那個星號代表的是各種字元的意思,因為 MV 系列實在有太多變形,例如說 MVC, MVVM 之類的。
(話說雖然很突然,但我剛看完報導者的造假.剝削.血淚漁場——跨國直擊台灣遠洋漁業真相,我覺得是很棒的一系列報導,有興趣的可以看看。)
其實這一篇原本是想讓大家理解一下各種 MV 開頭的架構有哪些異同,但我深思熟慮之後發現還是不要講會比較好。第一是因為我對這主題有沒那麼熟,第二是因為我覺得現在講這個對你們來說可能太早,反而會拖累你們。所以我們今天只談 MVC 就好。
MVC 是什麼?是一種架構。我覺得要談架構最好的方式,就是先來談「沒有架構」。架構絕對不是盤古開天以後就自己蹦出來的,而是經過長久的歷史累積下來之後所濃縮出來的智慧。在學一樣新的東西的時候,我超級無敵注重一個要素:「痛點」。所有技術一定都是因為要解決某個問題、某個痛點才被發明出來,假設今天你沒經歷過那個痛點,這項技術對你來說就可有可無,你甚至還不知道為什麼需要它。
所以我認為最好的方式,就是先讓你痛,然後才讓你懂。
大家有寫過 php 嗎?沒有的話沒關係,我現在當場隨便教你一些,因為 php 實在是一個很容易入門的程式語言。
先來介紹路由好了,php 一般的路由都是跟著檔案路徑而定,例如說你 php 的根目錄放在 /var/www
底下好了,那你在網址列輸入 http://localhost/index.php
,其實就是在試著存取/var/www/index.php
這個檔案。以此類推,http://localhost/users/test/123.php
,就是/var/www/users/test/123.php
。很簡單吧!檔名是什麼路徑就是什麼。
再來是程式的部分,我們直接來一段 php 的程式碼(但其實我只是照著概念寫,你拿這段程式碼去跑一定不會過):
<?
include "db.php"
db.init();
$data = mysqli_query('select * from users');
?>
<html>
<head>
</head>
<body>
<h1>使用者資料</h1>
<? for($i=0; $i<$data.length; $i++) { ?>
<div class="member">
<div>會員姓名:<? echo $data[i].name ?> </div>
<div>會員帳號:<? echo $data[i].username ?> </div>
</div>
<? } ?>
</body>
</html>
php 的變數都會在前面加上$
這個符號,你看久就習慣了。上面這段程式碼就是從資料庫撈東西出來,接著輸出成 HTML。你有沒有發現一件很神奇的是,就是你可以把所有的程式碼都寫在一起。其實上面的程式碼跟我們之前講過的 ejs
有八七分像,都可以把你的邏輯跟 HTML 寫在一起。
所以如果 ejs 也沒有任何限制的話,上面那段程式碼大概會變成:
<%
var db = require('./db');
db.init();
var data = db.query('select * from users');
%>
<html>
<head>
</head>
<body>
<h1>使用者資料</h1>
<% for(var i=0; i<data.length; i++) { %>
<div class="member">
<div>會員姓名:<%= data[i].name %> </div>
<div>會員帳號:<%= data[i].username %> </div>
</div>
<% } %>
</body>
</html>
你有沒有覺得這樣很方便?全部都寫在一個檔案裡面就好了。尤其是 php,甚至你連路由都不用設定,因為它直接幫你決定好了。舉例來說,如果你要寫一個簡單的留言板好了,你可能會需要幾個頁面:
就這樣就好了,三個檔案,立刻輕鬆完成了一個簡易版留言板。
可是,如果要用 express 來寫呢?你能立刻想出來要怎麼寫嗎?你還得先熟悉那些架構,熟悉怎麼設置 route,怎麼設置 view,怎麼去 render,那麼麻煩做什麼,我用 php 就好啦!
沒錯,當專案很小的時候當然是這樣沒錯。像是 express 這樣的框架反而會成為一種負擔,那是因為它提供的功能你都用不到。但是我們可以想想,當專案變大的時候會發生什麼事情?
假設今天老闆跟你說我們為了聖誕節,要給某些特別的用戶聖誕節特別版的介面,於是你就多加了兩個檔案:
接著又說需要一個後台管理功能,至少讓管理員可以刪除留言。所以你又新增了幾個檔案
做到這一步為止,你的資料夾裡面大概有 10 個 php 檔案了。然後老闆突然說一句:「那個導覽列有點醜,我們重新做一個吧!」,你就要把每一個 php 檔案的導覽列都換掉,做很多次複製貼上。這時候你就會很懷念 template engine 這種東西。
雖然說人生如此悲慘,但幸好公司新來了一個前端工程師,願意幫你改介面。於是之後功能面的全部歸你,介面全部歸那個新來的工程師,很不錯吧!省了很多事。可是你卻發現好像不是這樣,因為你們不能同時改同一個檔案。
例如說首頁好了,你可能想要加個功能,你就會去改 index.php,這時候那個前端工程師也想來改首頁的介面,就也會改 index.php。這時候就很麻煩了,各自改完之後還要去處理怎麼把兩邊的改動去合併起來。當然也可以互相協調誰要先改誰後改,但這些溝通也是要花很多成本的。
故事到這邊為止,你擁有的就是一個有 10 個 php 檔案,十分直覺、毫無架構的一個專案。
那我們再來看,如果有了 MVC 以後會變怎樣?先來簡單介紹一下 MVC,它是一種架構,是三個英文單字的縮寫。
例如說,像是上面那個留言板的例子,如果你要在首頁顯示全部留言的話,當使用者造訪 /index
的時候,你會有一個 controller 去處理這個 request,然後去跟 Model 拿所有留言的資料,當 Model 拿完回來以後,傳送到 View 去顯示出來。如果寫成程式碼的話大概是這樣:
var model = require('./model/comment');
route.get('/', function (req, res) {
var comments = model.getAll();
response.render('comments', {
comments: comments
})
})
你會發現你的程式碼被分割成一小部分一小部分。這個的優點就是你要改任何一個小地方,你都可以很明確的知道說你要去哪邊改,而且更重要的一點是:彼此不會互相干擾。
什麼意思呢?例如說你今天如果換了一個完全不同的 database 系統,怎麼辦?你有沒有發現我們上面 controller 的程式碼裡面,完全沒有任何跟資料庫有關的地方?因為我們都封裝在 model 裡面了,我們只是很簡單的呼叫:var comments = model.getAll();
,controller 並不用知道這個裡面是怎麼實做的,它只要去呼叫就好。
因此,所有跟資料相關的改動你都可以在 model 裡面完成,而且不會影響到其他部分。
View 也是一樣,你今天把 View 獨立出來以後,負責前端的工程師就可以專注在那些檔案上面。不會跟你要做的事情衝突到,因為你要做的事都會在 controller 或是 model 裡面完成。
所以簡單來說,有了 MVC 以後,你的程式處理 request 的步驟會變成是:
對比一下純 php:
最後來總結一下為什麼我們需要 MVC:
其實第三點是所有架構的基石。或者是說,所有程式相關的設計模式的基石都是這個了,原則就是「好維護、好修改」。所以我才說這些架構都是演化而來的,原本大家都是把所有東西混在一起寫的。可是寫著寫著發現怎麼越來越麻煩,我要改一個功能需要動到十個檔案,而且都在做一樣的事情。不行不行,我必須把常更改的的地方抽出來,不然太累了。於是就有了我們現在看到的這些架構。
所以我個人是認為你如果不懂這些架構也無所謂,你寫久了總有一天會自己領悟的。不是不報,只是時候未到。
總之,儘管我講了這麼多,你可能還是會覺得霧煞煞,不知道到底為什麼需要 MVC。那你可以先去學個純 PHP,完全不用任何框架的那種,然後找一個意見很多的老闆或是案主。當你因為需求不斷變動而一直在改程式碼,做一些重複性事情的時候,你再來看這篇文章,相信一定很有感觸。
最後附上幾篇跟 MV* 有關的各種好文,但其實你不讀也是可以,因為讀了你可能會頭很痛而且弄得更混亂。