iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 15
3
Modern Web

Half-Stack Developer 養成計畫系列 第 15

MV* 的愛恨情仇

  • 分享至 

  • xImage
  •  

MV* 的愛恨情仇

在開始之前,我們先來討論一下,上一篇最後讓大家想的問題:

  1. 為什麼要用 Express.js,而不是用我們上一章的 http.createServer?
  2. 老闆跟我說不准用任何框架,怎麼辦?
  3. 為什麼要用 template engine? 沒有它的話會怎麼樣?
  4. 為什麼要把 headers 獨立出來成一個檔案?寫在每一個 ejs 裡面這樣不好嗎?

下面是我自己對這幾題的想法:

  1. 你要用 http.createServer 當然也是可以,但就是什麼都要自己來。Express.js 這個現成框架已經幫我們包裝很多功能了,用這個比較方便快速。
  2. 只好自己動手做輪子了,幫你 qq
  3. 不用也可以,但就跟第一題一樣,自己做會比較麻煩一點,你要自己去訂一套規則。用現成的、已經被驗證可以用在 production 上的 library 會是比較有效率的做法。
  4. 在考慮一個東西要不要獨立抽取出來的時候,只要想一個問題就好:如果哪天你要改它,好改嗎?舉例來說,如果我們沒有抽出去,就代表每一個 ejs 裡面都會有重複的 code,當你今天發現打錯字,你就必須每一個檔案都去修正。如果抽取出來,只要改一個地方就好。這樣你就知道為什麼要獨立出來了。

其實會問上面這些問題,我只是想讓你知道「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,甚至你連路由都不用設定,因為它直接幫你決定好了。舉例來說,如果你要寫一個簡單的留言板好了,你可能會需要幾個頁面:

  1. 顯示所有留言的頁面(其實也就是首頁) index.php
  2. 填寫留言的表單 submit.php
  3. 發送留言之後處理的程式(你可以一樣寫在 submit.php,或是獨立出來變成 submit_action.php)

就這樣就好了,三個檔案,立刻輕鬆完成了一個簡易版留言板。

可是,如果要用 express 來寫呢?你能立刻想出來要怎麼寫嗎?你還得先熟悉那些架構,熟悉怎麼設置 route,怎麼設置 view,怎麼去 render,那麼麻煩做什麼,我用 php 就好啦!

沒錯,當專案很小的時候當然是這樣沒錯。像是 express 這樣的框架反而會成為一種負擔,那是因為它提供的功能你都用不到。但是我們可以想想,當專案變大的時候會發生什麼事情?

假設今天老闆跟你說我們為了聖誕節,要給某些特別的用戶聖誕節特別版的介面,於是你就多加了兩個檔案:

  1. index_xmas.php
  2. submit_xmas.php

接著又說需要一個後台管理功能,至少讓管理員可以刪除留言。所以你又新增了幾個檔案

  1. admin.php(顯示登入視窗)
  2. admin_action.php(送出表單之後,要判定是否登入成功的程式)
  3. admin_page.php(管理留言的介面)
  4. delete.php(按下刪除留言之後處理的程式)

做到這一步為止,你的資料夾裡面大概有 10 個 php 檔案了。然後老闆突然說一句:「那個導覽列有點醜,我們重新做一個吧!」,你就要把每一個 php 檔案的導覽列都換掉,做很多次複製貼上。這時候你就會很懷念 template engine 這種東西。

雖然說人生如此悲慘,但幸好公司新來了一個前端工程師,願意幫你改介面。於是之後功能面的全部歸你,介面全部歸那個新來的工程師,很不錯吧!省了很多事。可是你卻發現好像不是這樣,因為你們不能同時改同一個檔案。

例如說首頁好了,你可能想要加個功能,你就會去改 index.php,這時候那個前端工程師也想來改首頁的介面,就也會改 index.php。這時候就很麻煩了,各自改完之後還要去處理怎麼把兩邊的改動去合併起來。當然也可以互相協調誰要先改誰後改,但這些溝通也是要花很多成本的。

故事到這邊為止,你擁有的就是一個有 10 個 php 檔案,十分直覺、毫無架構的一個專案。

那我們再來看,如果有了 MVC 以後會變怎樣?先來簡單介紹一下 MVC,它是一種架構,是三個英文單字的縮寫。

  1. Model,跟資料有關的都放這裡
  2. View,跟介面有關的都在這裡
  3. Controller,負責協調上面兩者

例如說,像是上面那個留言板的例子,如果你要在首頁顯示全部留言的話,當使用者造訪 /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 的步驟會變成是:

  1. request 先進到 router,讓路由決定把 request 給哪個 controller 去處理
  2. controller 做一些事情,然後從 model 那邊拿到資料
  3. 把資料處理過後丟給 view,產生 HTML
  4. 由 controller 回傳 response

對比一下純 php:

  1. 由網址決定要執行哪一隻 php
  2. 在同一個檔案裡面完成取資料、輸出 HTML

最後來總結一下為什麼我們需要 MVC:

  1. 因為我們不希望程式之間不同功能牽扯得太深,這樣改一個就會動到另外一個
  2. 因為我們希望大家可以各司其職,各自負責各自的功能
  3. 好維護、好修改

其實第三點是所有架構的基石。或者是說,所有程式相關的設計模式的基石都是這個了,原則就是「好維護、好修改」。所以我才說這些架構都是演化而來的,原本大家都是把所有東西混在一起寫的。可是寫著寫著發現怎麼越來越麻煩,我要改一個功能需要動到十個檔案,而且都在做一樣的事情。不行不行,我必須把常更改的的地方抽出來,不然太累了。於是就有了我們現在看到的這些架構。

所以我個人是認為你如果不懂這些架構也無所謂,你寫久了總有一天會自己領悟的。不是不報,只是時候未到。

總之,儘管我講了這麼多,你可能還是會覺得霧煞煞,不知道到底為什麼需要 MVC。那你可以先去學個純 PHP,完全不用任何框架的那種,然後找一個意見很多的老闆或是案主。當你因為需求不斷變動而一直在改程式碼,做一些重複性事情的時候,你再來看這篇文章,相信一定很有感觸。

最後附上幾篇跟 MV* 有關的各種好文,但其實你不讀也是可以,因為讀了你可能會頭很痛而且弄得更混亂。

  1. MVC是一個巨大誤會
  2. 被误解的MVC和被神化的MVVM
  3. MVC,MVP 和 MVVM 的图示
  4. iOS 架構模式 – 簡述 MVC, MVP, MVVM 和 VIPER
  5. 浅谈开发中的MVVM模式及与MVP和MVC的区别
  6. [Architecture] MVP, MVC, MVVM, 傻傻分不清楚~

上一篇
有輪堪用直須用:Express.js
下一篇
你喜歡吃餅乾嗎?我是還好(session 與 cookie)
系列文
Half-Stack Developer 養成計畫30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
deh
iT邦研究生 1 級 ‧ 2020-03-26 16:40:31

從來沒碰過php
看到這篇才明白......
太適合初學者了吧!!!

我要留言

立即登入留言