期刊/重新探討Cakephp,Ruby on Rails與MVC(下)
出自台灣中等學校資訊管理人學會
目錄 |
[編輯] 摘要
- 投稿日期:2007/04/12
- 作者:李開文
[編輯] 重新探討Cakephp,Ruby on Rails與MVC(下)
[編輯] 權限系統的實例
有關需求分析的部分,請參考上集
[編輯] 沒有商業邏輯的實做
先講講這個情況,是模擬在Model沒有商業邏輯的情況下,意思就是我們不在Model裡寫方法。
我先思考兩個方向:每個頁面撰寫自己的權限控制,不然所有頁面繼承同樣的權限控制方法(也就是寫在ApplicationController裡)。
當然你說,為啥不能夠每個頁面自己做檢查?當然是可以。 一旦頁面多達上百個,而權限的原則改變,要改寫程式碼時,就會掉淚了。
那我只好乖乖地繼承了。
class ApplicationController
model :user,:role
private
def check_login
if(!session[:user])
redirect_to :controller=>'user',:action=>'login'
end
def check_admin_role
if(!check_login) return false;
if(session[:user].role.id==0)
return true
end
#顯示類似「權限不足」的訊息
false
end
end
class RoleController < AppliactionController
def list check_login check_admin_role #載入roles並且列出他們,做該做的工作 ... end
end
既然將權限控制繼承了,也就是說一旦開始有每個頁面對權限控制有不同的需要,就完蛋了。
def check_admin_role
if(!check_login) return false;
if(session[:user].role.id==0)
return true
end
if(session[:user].role.id<=1 && controller_name=='ctl1' && action_name=='act1') #另一個頁面
#嗯...
end
if(session[:user].role.id<=2 && controller_name=='ctl2' && action_name=='act2') #再一個頁面
#挖咧...
end
#接下去的還是別寫好了...
false
end
另外我們來看看使用者,根據上述的前提,又因為認證功能在很多頁面都有可能使用,我還是得繼承。
class ApplicationController
def user_authenticate(username,password)
auth_passed=true
if(!User.find(:first,["username=?,password=?",username,password]))
auth_passed=false
end
#選擇性的第三方認證,假設是google
if(params[:google_auth_checked])
#開始google auth
##你會把一堆程序寫在這##
#google auth結束
end
#如果還有更多第三方認證方式...會一直往下加
auth_passed
end
end
我曾經用這種方式撰寫了約500多行複雜的商業程序。
不管你怎樣寫結果一樣複雜,看起來我在挖個陷阱讓大家跳。不過這種類似的情況普遍地存在在許多的專案中,尤其是一剛開始快速開發,然後時間一久又發現需求開始變更的這種專案。而典型解決的方法,也就是各位程式設計師們的苦工。此外,現在有了MVC的寫法,如果沒有仔細思考Model的設計,也會自然而然地將這些方法往Controller那邊寫,久了也會造成這種情況。
[編輯] 有設計並有商業邏輯的實做
設計
根據上面的需求,我就開始思考,User到底該幹啥?Role到底該幹啥?不是與該類別相關的方法就不要加入,並將這些想法化成實際的圖。
變成類別圖看起來會像這樣。在UML中,有加上底線的是static method。
因為是WebApplication加上我想要使用Framework,所以我也預定要有這些controller,並將他們的view(method)標上。
實做
假設我已經將User,Role兩個寫好了,其中User
class User
has_many :role ...
end
接著我就可以:
class RoleController
model :user
def list
if(!session[:user])
redirect_to :controller=>'user',:action=>'login'
return
end
if(!session[:user].role.is_registered_user?)
#顯示"權限不足"
end
if(session[:user].role.is_admin?)
#顯示管理員功能
end
end
end
看起來,寫在role.is_admin?的程式我當然還是簡單地去用role.id去判斷,但在沒花多久時間簡單思考的設計中,做了幾件很重要的事情。
一是將前面例子的check_admin_role中,真正和Role有關係的程式碼重構到Role類別之中。就算是快速開發的專案,發現一樣的問題,要進行重構,也還是會做這件事情。
二是role.is_admin?只會做跟role和admin有關係的事情,不會再牽扯到controller或action。一個類別的一個方法只做好他該做的事,這個是在大系統中撰寫物件導向的目的,也就是進行鬆散整合(loosely coupling)。
但畢竟靜態方法,也是有點像進行重構,再看看另一個例子。
class User
def authenticate(password)
return self.password==Digest::MD5.hexdigest(password)
end
def google_auth(password)
#開始google auth
##你會把一堆程序寫在這##
#google auth結束
end
end
那麼,不管是啥頁面,你都可以方便使用,也不用在繼承的方法裡一直地加下去。
class UserController
model :user #avoid session restore error
def login
user=User.find(:first,["username=?",params[:username]])
if(!user)
#顯示未註冊錯誤
end
if(!user.authenticate(params[:password]))
#顯示密碼錯誤
end
if(params[:google_auth_checked] && !user.google_auth(params[:password]))
#顯示google認證錯誤
end
end
end
[編輯] 結論
雖然在這篇http://lightyror.thegiive.net/2007/01/active-record.html 文章有有闡述到,user.mail.read這樣的寫法寫起來比較順,但是其實我認為問題是設計上去影響到實做,並不是實做起來比較好寫而已。
當然並不是每樣東西都要套上MVC才肯罷休,MVC只是提出一個觀念:畫面,控制邏輯,商業邏輯與資料的分離。為何需要鬆散整合?為何需要物件導向?為何需要軟體工程或設計樣式?現在很多新一代的程式設計師都有很好的編程能力,但卻對以上一無所知,只是因為大家都這樣寫,文章都這樣介紹。加上許多快速開發的Framework,都強調讓設計師好維護,快速撰寫,所見即所得。難不成這些觀念,會因為這些快速的Framework而就變得不需要了嗎?一旦系統變大,數十個table,上百個頁面,就算再快速開發的Framework也不見得解決問題,還是全部都會回歸到設計的問題去。
經過之前的專案後,我發現,設計觀念更是重要,因為這些Framework本來就是依照設計樣式的觀念撰寫的。觀念一錯,就會造成誤用,誤用多的結果,就會效能低落。你會花上很多重複的程式碼去完成一件結構上很簡單的事,或是因為想要很高度的彈性而過度結構化(或者是過度鬆散)。其實在上述的例子,與其說是重構,或是改變寫法,不如說好好地思考並撰寫商業邏輯就可以解決了。
在這裡當然也要為Ruby on Rails美言幾句,實際上在比較過許多的Framework後,我發現Rails對於兩種Web程式設計師能夠帶來好處。 1. 如果你是新手,Rails的Generator,Scaffold能夠為你帶來快速開發的好處。 2. 如果你是老手,Ruby語法的快捷,以及Rails開發團隊為你撰寫許多彈性的功能,讓你反而不會被Framework所限制住。
參考連結
http://www.ccw.com.cn/server/jssc/htm2006/20061226_232112.shtml
