emu in blogjava

            BlogJava :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理 ::
            171 隨筆 :: 103 文章 :: 1052 評(píng)論 :: 2 Trackbacks

          反思以前對(duì)“多對(duì)多”關(guān)系處理的設(shè)計(jì)

          很多公司的筆試題都喜歡考這一道:有n個(gè)供貨商和m種貨品,每個(gè)供貨商可能供應(yīng)多種貨品,每種貨品也有可能由多個(gè)供貨商供應(yīng),建立相應(yīng)的數(shù)據(jù)庫(kù)表維護(hù)供應(yīng)商和貨品以及他們的供貨關(guān)系。

          這類多對(duì)多問(wèn)題其實(shí)在真實(shí)的系統(tǒng)中很常見(jiàn),比如用戶--角色,角色--權(quán)限,用戶--用戶組等等。對(duì)接觸過(guò)類似的系統(tǒng)的人這當(dāng)然不是問(wèn)題,三個(gè)表,套路來(lái)的嘛。

          當(dāng)然有其他方式,比如供貨商表中添一個(gè)貨品列表字段,用xml或者約定的格式保存貨品列表,但是這樣一是查詢更新效率很低(查詢的時(shí)候要解析數(shù)據(jù),更新的時(shí)候要解析并重新組合數(shù)據(jù),反向查找的時(shí)候更低效),二是沒(méi)有辦法建立外鍵約束。對(duì)第反向查找的問(wèn)題,如果我們?cè)试S數(shù)據(jù)冗余并且有信心維護(hù)好數(shù)據(jù)一置性的話可以同時(shí)在貨品表中添加一個(gè)供貨商列表字段。但是除了效率、約束(以及冗余,假如它也算問(wèn)題的話)之外,還有個(gè)最重要的問(wèn)題我們忽略了:這樣的設(shè)計(jì)不優(yōu)雅。

          但是在數(shù)據(jù)庫(kù)表之外,我們做系統(tǒng)的時(shí)候是怎么處理類對(duì)應(yīng)關(guān)系的呢?我沒(méi)有看到過(guò)別人的設(shè)計(jì),但是在我的系統(tǒng)中使用的其實(shí)是類似于上面說(shuō)的第二種方式,比如在用戶對(duì)象中有一個(gè)角色列表字段,存儲(chǔ)了與用戶關(guān)聯(lián)的角色。當(dāng)然解析數(shù)據(jù)帶來(lái)的效率問(wèn)題不存在了,因?yàn)榻巧畔⑹且栽挤绞奖4娴模粩?shù)據(jù)一置性問(wèn)題也由數(shù)據(jù)庫(kù)解決。系統(tǒng)工作的很穩(wěn)定也很快,但是我們一直忽略了這個(gè)問(wèn)題:這樣的設(shè)計(jì)優(yōu)雅嗎?我認(rèn)為我們可以做的更好。

          假如我們象設(shè)計(jì)數(shù)據(jù)庫(kù)表一樣的設(shè)計(jì)系統(tǒng),系統(tǒng)本來(lái)可以做成這樣子的:

                                    (Database)
          tb_user          tb_user_role            tb_role
          ------------------------------------------------------------------------------------
          UserDAO          UserRoleDAO             RoleDAO
          UserMng            UserRoleMng              RoleMng

          這樣user的model對(duì)象里面就不需要維護(hù)role列表了。我們需要訪問(wèn)關(guān)聯(lián)關(guān)系的時(shí)候可以通過(guò)UserRoleMng來(lái)獲得列表,如果我們需要訪問(wèn)role對(duì)應(yīng)的user列表也一樣的簡(jiǎn)單(在原來(lái)的設(shè)計(jì)中要遍歷全部user)。

          更進(jìn)一步,我們把對(duì)這種多對(duì)多關(guān)聯(lián)關(guān)系的處理抽取出來(lái),做一個(gè)基類:
          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或者其他類似的關(guān)系管理器都可以繼承自AbstractRelationManager并由超類處理關(guān)聯(lián)查詢的邏輯:
          class UserRoleMng{
              public void addRelation(userId,roleId){
              super.addRelation(userId,roleId);
              }
          ...
          }

          如果高興還可以這樣包裝
          class UserRoleMng{
              public void addRelation(user,role){
              super.addRelation(user.getId,role.getId);
              }
          ...
          }

          這樣的設(shè)計(jì)看起來(lái)比原來(lái)的要好一點(diǎn)了,但是有一個(gè)問(wèn)題是,按我的經(jīng)驗(yàn)AbstractRelationManager很可能需要使用模版方法模式(templet method)來(lái)實(shí)現(xiàn),這樣不可避免的會(huì)違反依賴倒易原則(DIP)并降低代碼的可讀性,與我們的初衷有些背離。當(dāng)然templet method也可以用代理類來(lái)代替,但是這樣的實(shí)現(xiàn)在我看來(lái)不但復(fù)雜而且更不優(yōu)雅。另一種代替的方式是不采用templet method而把對(duì)模版方法的調(diào)用實(shí)現(xiàn)在每個(gè)子類中,這樣要求每個(gè)子類嚴(yán)格的符合編碼約定,而且?guī)?lái)了拷貝代碼的臭味。相比之下,我還是寧可選擇在必要的時(shí)候templet method。

          最后,我們有可能需要處理更復(fù)雜的關(guān)系,比如:

          group------group-------user
           |           |---------user
           |         group-------group------user
           |           |           |---------user
           |           |---------user
           |---------user

          user-group關(guān)系的一方,group是具有遞歸的層次結(jié)構(gòu)的對(duì)象。這中情況下我們大概還需要從AbstractRelationManager繼承一個(gè)新的抽象類
          abstract AbstractRecurveRelationManager AbstractRelationManager{
              protected List getId1ListById2Recurve(id2){
              //遞歸方法id2對(duì)應(yīng)的對(duì)象的子樹(shù)并獲得所有樹(shù)節(jié)點(diǎn)的id1列表。
              }


          假如多對(duì)多關(guān)系的雙方都具有層次結(jié)構(gòu)呢?一時(shí)間沒(méi)有想起來(lái)有這樣的實(shí)際例子,就不繼續(xù)擴(kuò)充類了。

          posted on 2005-07-13 14:44 emu 閱讀(1099) 評(píng)論(0)  編輯  收藏

          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 兰州市| 灯塔市| 六安市| 清丰县| 柘城县| 葵青区| 浮梁县| 北流市| 岳阳县| 南木林县| 合肥市| 尤溪县| 东平县| 曲阜市| 武冈市| 昌乐县| 巢湖市| 翼城县| 平顺县| 阜康市| 平塘县| 光泽县| 花莲市| 英德市| 临江市| 永顺县| 沁阳市| 海晏县| 阳新县| 牟定县| 建宁县| 桃江县| 达日县| 兴化市| 巫溪县| 日土县| 陆良县| 丹凤县| 东乡族自治县| 明溪县| 福安市|