反思以前對“多對多”關系處理的設計
很多公司的筆試題都喜歡考這一道:有n個供貨商和m種貨品,每個供貨商可能供應多種貨品,每種貨品也有可能由多個供貨商供應,建立相應的數據庫表維護供應商和貨品以及他們的供貨關系。
這類多對多問題其實在真實的系統中很常見,比如用戶--角色,角色--權限,用戶--用戶組等等。對接觸過類似的系統的人這當然不是問題,三個表,套路來的嘛。
當然有其他方式,比如供貨商表中添一個貨品列表字段,用xml或者約定的格式保存貨品列表,但是這樣一是查詢更新效率很低(查詢的時候要解析數據,更新的時候要解析并重新組合數據,反向查找的時候更低效),二是沒有辦法建立外鍵約束。對第反向查找的問題,如果我們允許數據冗余并且有信心維護好數據一置性的話可以同時在貨品表中添加一個供貨商列表字段。但是除了效率、約束(以及冗余,假如它也算問題的話)之外,還有個最重要的問題我們忽略了:這樣的設計不優雅。
但是在數據庫表之外,我們做系統的時候是怎么處理類對應關系的呢?我沒有看到過別人的設計,但是在我的系統中使用的其實是類似于上面說的第二種方式,比如在用戶對象中有一個角色列表字段,存儲了與用戶關聯的角色。當然解析數據帶來的效率問題不存在了,因為角色信息是以原始方式保存的;數據一置性問題也由數據庫解決。系統工作的很穩定也很快,但是我們一直忽略了這個問題:這樣的設計優雅嗎?我認為我們可以做的更好。
假如我們象設計數據庫表一樣的設計系統,系統本來可以做成這樣子的:
(Database)
tb_user tb_user_role tb_role
------------------------------------------------------------------------------------
UserDAO UserRoleDAO RoleDAO
UserMng UserRoleMng RoleMng
這樣user的model對象里面就不需要維護role列表了。我們需要訪問關聯關系的時候可以通過UserRoleMng來獲得列表,如果我們需要訪問role對應的user列表也一樣的簡單(在原來的設計中要遍歷全部user)。
更進一步,我們把對這種多對多關聯關系的處理抽取出來,做一個基類:
abstract class AbstractRelationManager{
protected void init();
protected void addRelation(id1,id2){
...
}
protected void deleteRelation(id1,id2)
...
}
protected List getId1ListById2(id2){
...
}
protected List getId2ListById1(id1){
...
}
}
然后UserRoleMng、RolePermissionMng或者其他類似的關系管理器都可以繼承自AbstractRelationManager并由超類處理關聯查詢的邏輯:
class UserRoleMng{
public void addRelation(userId,roleId){
super.addRelation(userId,roleId);
}
...
}
如果高興還可以這樣包裝
class UserRoleMng{
public void addRelation(user,role){
super.addRelation(user.getId,role.getId);
}
...
}
這樣的設計看起來比原來的要好一點了,但是有一個問題是,按我的經驗AbstractRelationManager很可能需要使用模版方法模式(templet method)來實現,這樣不可避免的會違反依賴倒易原則(DIP)并降低代碼的可讀性,與我們的初衷有些背離。當然templet method也可以用代理類來代替,但是這樣的實現在我看來不但復雜而且更不優雅。另一種代替的方式是不采用templet method而把對模版方法的調用實現在每個子類中,這樣要求每個子類嚴格的符合編碼約定,而且帶來了拷貝代碼的臭味。相比之下,我還是寧可選擇在必要的時候templet method。
最后,我們有可能需要處理更復雜的關系,比如:
group------group-------user
| |---------user
| group-------group------user
| | |---------user
| |---------user
|---------user
user-group關系的一方,group是具有遞歸的層次結構的對象。這中情況下我們大概還需要從AbstractRelationManager繼承一個新的抽象類
abstract AbstractRecurveRelationManager AbstractRelationManager{
protected List getId1ListById2Recurve(id2){
//遞歸方法id2對應的對象的子樹并獲得所有樹節點的id1列表。
}
}
假如多對多關系的雙方都具有層次結構呢?一時間沒有想起來有這樣的實際例子,就不繼續擴充類了。