首頁‎ > ‎電子期刊‎ > ‎Ruby on Rails‎ > ‎

重新探討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



Comments