我愛(ài)oo,我愛(ài)java

          交流blog QQ:421057986 oofrank@donews

          2006年1月19日 #

          DAO-持久層-領(lǐng)域?qū)ο?貧血模型

          原文


          關(guān)于"貧血模型"的討論幾乎沒(méi)有停止過(guò),在openfans.org的開(kāi)發(fā)過(guò)程中,我們也討論了很久,我覺(jué)的有很多東西應(yīng)該記下來(lái):
          明確一下意思先:
          DAO:數(shù)據(jù)操作對(duì)象,會(huì)操作數(shù)據(jù)庫(kù)
          持久層:能提供對(duì)象持久化服務(wù)的一系列組件或服務(wù)
          領(lǐng)域?qū)ο?描述領(lǐng)域模型的對(duì)象,是通過(guò)業(yè)務(wù)分析進(jìn)行系統(tǒng)建模的產(chǎn)物
          貧 血模型:就是domain object只有屬性的getter/setter方法的純數(shù)據(jù)類,所有的業(yè)務(wù)邏輯完全由一個(gè)所謂的Manager來(lái)完成(又稱 TransactionScript),這種模型下的domain object被Martin Fowler稱之為“貧血的domain object”
          常見(jiàn)的類基本結(jié)構(gòu)如下:
          一個(gè)業(yè)務(wù)數(shù)據(jù)類叫做Item,
          一個(gè)DAO接口類叫做ItemDao
          一個(gè)DAO接口實(shí)現(xiàn)類叫做ItemDaoHibernateImpl
          一個(gè)業(yè)務(wù)邏輯類叫做ItemManager(或者叫做ItemService).

          觀察上面的幾個(gè)類很容易發(fā)現(xiàn)問(wèn)題:
          1:Item和ItemManager實(shí)際是操作與數(shù)據(jù)的關(guān)系,實(shí)際完成的就是經(jīng)典OO中的一個(gè)對(duì)象的能力;
          2:當(dāng)有許多Item時(shí) 類組變得很龐大,產(chǎn)生很多 xxxDao xxxImpl xxxManager 其中包含大量重復(fù)代碼;
          按<<重構(gòu)>>的觀點(diǎn),上述代碼存在以下臭味:
          1:重復(fù)的代碼?? xxxDao xxxImpl xxxManager(通常)
          2:霰彈式修改,一個(gè)變化影響多個(gè)類,類之間不夠高內(nèi)聚 item變化-->Dao,Impl,Manager均要變動(dòng)
          3:依戀情結(jié),兩個(gè)類之間互相作用過(guò)多 item<->Manager
          4:平行繼承體系,當(dāng)增加一個(gè)新類時(shí)總是要增加另一個(gè)類
          5:夸夸其談未來(lái)性,在沒(méi)有任何暗示的情況下考慮擴(kuò)展? Dao,實(shí)際HibernateImpl可能n年內(nèi)是唯一的Dao實(shí)現(xiàn)
          6:純稚的數(shù)據(jù)類,只有數(shù)據(jù)的類? item

          我覺(jué)的 貧血模型 是系統(tǒng)分析設(shè)計(jì)方向性錯(cuò)誤的產(chǎn)物:
          1:沒(méi)有進(jìn)行領(lǐng)域建模---以數(shù)據(jù)表結(jié)構(gòu)為中心,而不是業(yè)務(wù)模型為中心的思考方式,使設(shè)計(jì)人員選擇Item為考慮問(wèn)題的出發(fā)點(diǎn)
          2:將DAO與持久層混淆---我們需要的一種持久化服務(wù),DAO緊緊是提供數(shù)據(jù)操作能力而已,Hibernate是一種高級(jí)的服務(wù)(他已經(jīng)包含了DAO,而不是相反),已經(jīng)完成了所有的持久層服務(wù).
          3:過(guò)于強(qiáng)調(diào)低偶合---將一些本來(lái)一些提供單一職責(zé)的內(nèi)容分散在多個(gè)單元中使 客戶端 依賴更多的接口,而忘記了高內(nèi)聚原則.
          4:Spring的能力限制---由于Spring現(xiàn)階段不支持對(duì)于領(lǐng)域模型的服務(wù)注入,使設(shè)計(jì)人員將操作和數(shù)據(jù)分開(kāi),并將領(lǐng)域變?yōu)镈ataOnly的.
          ? (Spring2.0將在很大程度上解決這個(gè)問(wèn)題)
          ?
          我認(rèn)為良好的解決方案:
          ? 首先領(lǐng)域建模,建立領(lǐng)域模型-->合并前面所說(shuō)的Item和ItemManager成為 domainItem;對(duì)于數(shù)據(jù)庫(kù)服務(wù),
          ? 1:如果考慮領(lǐng)域?qū)影瑪?shù)據(jù)操作能力,則建立DAO并選擇其它好的DAO方案比如IBATIS或Hibernate之類的組件;
          ? 2:如果考慮將數(shù)據(jù)庫(kù)(或其他存儲(chǔ)界質(zhì))存儲(chǔ)考慮在領(lǐng)域之外成為持久層,
          ? ??? a:則或者對(duì)持久層框架同時(shí)建模,同時(shí)選擇合適的組件為持久層服務(wù)提供存儲(chǔ)服務(wù)(包括DAO--亦可選擇IBATIS/Hibernate組件),
          ? ??? b:或者直接使用Hibernate/JDO等框架實(shí)現(xiàn)持久化服務(wù),領(lǐng)域?qū)又苯邮褂贸志脤臃?wù),對(duì)領(lǐng)域?qū)ο筮M(jìn)行持久化和反持久化(從持久層獲取以持久化的對(duì)象).
          ?
          其他:
          ? 實(shí)際上,作為一種解決方案,所謂"貧血模型"的具體使用,并不會(huì)有太大的問(wèn)題,尤其是使用一些代碼生成工具或已經(jīng)做好相應(yīng)的基本框架時(shí),很多軟件的核心價(jià) 值都在于對(duì)客戶提供的服務(wù),而其內(nèi)部則成為黑盒,我們只要合理的解決業(yè)務(wù)問(wèn)題,就是"王道"了,對(duì)于代碼的臭味,可以慢慢重構(gòu)--這也需要成本呀.?
          ?
          再其他:
          有人說(shuō),我們的業(yè)務(wù)就是CRUD,領(lǐng)域模型只有數(shù)據(jù)類就足夠了.我覺(jué)的這是搞錯(cuò)了方向------只有CRUD時(shí),只有處理CRUD的那些類才有必要進(jìn)行建模(他們才是領(lǐng)域模型),而所謂的User\Item等數(shù)據(jù)類則完全沒(méi)有必要進(jìn)行建模,更不要談?lì)I(lǐng)域了.

          貧血之外:
          實(shí)際上,軟件\OO方法的外延大的很,更多問(wèn)題與數(shù)據(jù)庫(kù)存儲(chǔ)無(wú)關(guān)(但也有貧血問(wèn)題),所以建模才是根本,OO方法的原則才是我們必須掌握的.

          posted @ 2006-04-10 22:21 兼聽(tīng)則明 閱讀(6559) | 評(píng)論 (4)編輯 收藏

          SQLServer的一個(gè)bug

          SQLServer一個(gè)bug終于被我碰上了

          我有一個(gè)表使用字符類型存儲(chǔ)數(shù)字值,想進(jìn)行匯總計(jì)算:

          sum(case when isnumeric(FieldName)=0 then 0 else cast  (FieldName as numeric) end)
          簡(jiǎn)單試了一下沒(méi)有問(wèn)題,可是今天數(shù)據(jù)中有一個(gè) ’2.1234567E7‘  isnumeric返回1 cast 返回錯(cuò)誤

          嗚嗚。。。。
          怎么辦......

          posted @ 2006-02-05 22:37 兼聽(tīng)則明 閱讀(350) | 評(píng)論 (0)編輯 收藏

          使用Quartz要注意的一個(gè)問(wèn)題


          當(dāng)設(shè)置一個(gè)Schedule的startDate早于 new Date(),并且調(diào)度周期又觸發(fā)于startDate和new Date()之間時(shí),就會(huì)立即觸發(fā)當(dāng)前job。簡(jiǎn)單的解決方式是將startDate設(shè)為new Date().

          posted @ 2006-01-23 00:52 兼聽(tīng)則明 閱讀(435) | 評(píng)論 (0)編輯 收藏

          使用abator自動(dòng)生成ibatis代碼的經(jīng)驗(yàn) 及碰到的問(wèn)題的解決方案


          1:abator下載:http://ibatis.apache.org/abator.html
          2:將abator安裝到eclipse中
          3:此時(shí)可以新建一種文件類型:Abator for iBATIS Configuration File,建立一個(gè)
          4:在 jdbcConnection 中設(shè)置要mapping的數(shù)據(jù)庫(kù)的jdbc連接
            classPathEntry 是你的jdbc driver類路徑
          5:javaModelGenerator,sqlMapGenerator,daoGenerator 分別設(shè)置 java dataObject、sql mapping文件和 DAO 接口、實(shí)現(xiàn)類的生成位置:targetPackage 目標(biāo)包,targetProject:eclipse項(xiàng)目
          6:daoGenerator 中可以設(shè)置屬性  type: ibatis 或 spring 指定生成的dao實(shí)現(xiàn)類是使用com.ibatis.dao.client.template.SqlMapDaoTemplate
          還是
          org.springframework.orm.ibatis.support.SqlMapClientDaoSupport
          7: table 中 tableName 指定要處理的表名
            可以有多個(gè)table
          8:table中可以包含子元素 generatedKey: 使Insert方法可以返回值--由指定的column mapping
          9:generatedKey中的sqlStatement屬性可以是獲取sequence的SQL,也可以是獲取自增值的SQL
            比如:Oracle的 select theSequence.nextVal from dual
                 SQLServer的 SELECT @@IDENTITY as  column_name
          10:保存文件,選中文件,右鍵菜單選擇Generate iBATIS Artifacts! ok...



          使用abtor生成的iBatis代碼出現(xiàn)xml解析錯(cuò)誤的解決方案
          如果按上述方式生成的代碼有xml解析錯(cuò)誤:  請(qǐng)下載這個(gè)

          注意,該文件名為Abator.rar.txt實(shí)際是一個(gè)rar文件,只是上傳服務(wù)器有文件類型限制 所以只好加了擴(kuò)展名txt。
          請(qǐng)去掉.txt后解壓。

          使用
          org.apache.ibatis.abator.core_0.5.1.jar
          替換調(diào)你的 eclipse\plugins 的同名文件 即可。

          然后重新生成代碼。 OK 應(yīng)該可以咯....

          我改了一點(diǎn)代碼,需要可以留言。

          posted @ 2006-01-21 13:51 兼聽(tīng)則明 閱讀(9085) | 評(píng)論 (12)編輯 收藏

          在eclipse-plugin開(kāi)發(fā)中碰到的怪問(wèn)題:(eclipse 3.1.1 + wtp1.0)

          一個(gè)popupMenus Extensions,
          objectContribution:objectClass*: org.eclipse.core.resources.IFile

          在action的Class代碼中:
          public void selectionChanged(IAction action, ISelection selection) {
              StructuredSelection ss = (StructuredSelection) selection;
                  this.selectedFile==(IFile)ss.getFirstElement(); //此處拋出異常
          }

          上述代碼的異常非常奇怪:
          根據(jù)的的跟蹤,ss.getFirstElement()返回值是File,該類實(shí)現(xiàn)了IFile接口,
          而且我用 ss.getFirstElement().getClass().isAssignableFrom(IFile.class)返回是false;
          真是奇怪!---有人知道為什么嗎?

          另外在實(shí)踐eclipse plugin開(kāi)發(fā)過(guò)程中也有幾個(gè)心得:(肯定能用,但未必最佳)

          1、如果開(kāi)發(fā)plugin,所有的依賴庫(kù)都要包含到 Plug-in Dependencies 中;而不能只是引入到工程中。
          2、如何輸出到console:
          MessageConsole mc=new MessageConsole("****",null);
          IConsole[] cs=new IConsole[1];
          cs[0]=mc;
          ConsolePlugin.getDefault().getConsoleManager().addConsoles(cs);
          mc.activate();
          PrintStream out=new PrintStream( mc.newOutputStream());
          out.println("*******.");
          3、如何獲取依賴工程的輸出路徑:
          selectedProject:當(dāng)前工程---由用戶選擇
          String[] ps= selectedProject.getRequiredProjectNames();                
          IWorkspace w= selectedProject.getProject().getWorkspace();
          for(int i=0;i<ps.length;i++){
          IResource r=w.getRoot().findMember(ps[i]);
          try{
              IJavaProject jp=new JavaProject((IProject)r,null);                
              File source=new File(jp.getProject().getLocation().append(jp.getOutputLocation().removeFirstSegments(1)).toOSString());
                  //作你的事情.....
          }catch(Exception e){
                //不是javaProject                                
              e.printStackTrace();                            
          }                
          4、如何使用進(jìn)度Dialog:
          Shell shell = new Shell();
          ProgressMonitorDialog dialog = new ProgressMonitorDialog(shell);        
          IRunnableWithProgress thread = new SomeRunner(shell);            
          dialog.run(true, false, thread);
          //=============================
          private class SomeRunner implements IRunnableWithProgress {
              public void run(IProgressMonitor monitor)throws InvocationTargetException, InterruptedException {
              monitor.beginTask("一些信息", 數(shù)值-總工作量);
                  for(;;){
                  // 一些工作
                  monitor.worked(數(shù)值-已完成工作量); //實(shí)際中,我得情況不太相符,不明白,但差不多 :(
                  monitor.setTaskName("一些信息");
               // 一些工作    
                 }
                  monitor.done();
              }
          }


          posted @ 2006-01-20 18:45 兼聽(tīng)則明 閱讀(603) | 評(píng)論 (0)編輯 收藏

          Spring 中的異常悖論

          這個(gè)題目可能有些危言悚聽(tīng),使用Spring的同行不用害怕,只是因?yàn)楣ぷ髦信龅揭恍﹩?wèn)題,才偶然想到這個(gè)問(wèn)題。

          首先要明確兩個(gè)問(wèn)題:
          1、系統(tǒng)隔離原則:
              即系統(tǒng)間依賴應(yīng)該是清晰的,不因?yàn)橐粋€(gè)系統(tǒng)的故障影響其他系統(tǒng),甚至整個(gè)系統(tǒng)。
          2、簡(jiǎn)單應(yīng)用習(xí)慣:
              普通程序員只會(huì)處理checked Exception,負(fù)責(zé)任的程序員會(huì)處理被調(diào)用的函數(shù)可能拋出的異常(根據(jù)源碼或javadoc)
           

          我們碰到一個(gè)情況:
          使用Spring的 init 特性初試化一個(gè)bean--一個(gè)使用Qutarz的調(diào)度程序。初試化過(guò)程中會(huì)拋出RuntimeException,從而造成Spring容器的整個(gè)初試化失敗。首先我們修改了程序,捕獲了所有異常,隨后在編碼指南中加入了一句話:"所有使用Spring-init機(jī)制初試化的類必須在init中捕獲所有異常:Exception"。因?yàn)橹挥腥绱瞬拍鼙WC整個(gè)系統(tǒng)不會(huì)因?yàn)榫植繂?wèn)題而完全癱瘓。

          我覺(jué)得這是Spring對(duì)異常處理的一個(gè)悖論:
          正方:底層異常都封裝成Runtime的,經(jīng)過(guò)幾次包裝--->簡(jiǎn)單應(yīng)用習(xí)慣--->異常被拋出領(lǐng)域?qū)?init場(chǎng)景下:由Spring處理)
          反方: 一個(gè)類的失敗可以造成整個(gè)系統(tǒng)的失敗---->Spring被RuntimeException宕掉---->不符合系統(tǒng)隔離原則


          當(dāng)然充分的測(cè)試可能可以解決這個(gè)問(wèn)題:但是要注意,測(cè)試只能證明系統(tǒng)有bug,不能證明系統(tǒng)沒(méi)有bug。

          解決方案:
          1、好的設(shè)計(jì)習(xí)慣:將應(yīng)該隔離的系統(tǒng)隔離開(kāi)-->使用不同的Spring配置文件.
          2、接口層錯(cuò)誤處理:在接口層應(yīng)該盡量對(duì)可以處理的異常進(jìn)行處理,然后以合理的方式傳遞給上層.


          PS:在與人交互的系統(tǒng)中都應(yīng)該給最終用戶合理的錯(cuò)誤提示,所以表現(xiàn)層應(yīng)該盡量捕獲非業(yè)務(wù)的RuntimeException給最終用戶更好的操作感受。

          posted @ 2006-01-19 21:45 兼聽(tīng)則明 閱讀(1465) | 評(píng)論 (19)編輯 收藏

          白馬非馬的面向?qū)ο蠓治?/a>

           公孫龍,六國(guó)時(shí)辯士也。疾名實(shí)之散亂,因資材之所長(zhǎng),為“守白”之論。假物取譬,以“守白”辯,謂白馬為非馬也。

            以馬作為進(jìn)行問(wèn)題域進(jìn)行建模,已知存在白馬這種類型。顯然存在馬的超類,并且馬類包含一個(gè)屬性-顏色,是否需要建立白馬的子類呢?顯而易見(jiàn)的是,當(dāng)馬的顏色屬性是白色時(shí),馬的一些實(shí)例表達(dá)了一個(gè)白馬的特殊實(shí)例群(由此我們可以得知:白馬顯然是馬),根據(jù)里氏替換原則,子類型必須能夠替換掉它們的基類型,顯然在分析了馬的行為模式以后,我們可以得出結(jié)論:白馬可以替換馬。----!難道真的要建立白馬、黑馬、X馬的子類嗎?
             我認(rèn)為可以從以下幾方面進(jìn)行分析。

          1、類的職責(zé)(很大程度上等同于服務(wù)能力,操作方式):
              設(shè)計(jì)一個(gè)類,首先要從類職責(zé)的分析入手,一個(gè)類要承擔(dān)響應(yīng)的職責(zé),反過(guò)來(lái)說(shuō)同樣的職責(zé)應(yīng)該由同樣的類承擔(dān),否則會(huì)造成類泛濫,實(shí)例孤單的狀況。如果領(lǐng)域內(nèi)馬和白馬承擔(dān)同樣的職責(zé),應(yīng)該只建立馬一個(gè)類,不應(yīng)該只見(jiàn)樹(shù)不見(jiàn)林,造成不抽象的類的產(chǎn)生。

          2、類的行為模式(當(dāng)類承擔(dān)響應(yīng)職責(zé)時(shí),如何擴(kuò)展):
             分析一個(gè)類要從類的行為模式入手,既然一個(gè)類要承擔(dān)責(zé)任,其承擔(dān)的責(zé)任表現(xiàn)方式是否一樣呢呢?這就是她的行為模式,這也是里氏替換原則主要起作用的地方,如果兩個(gè)類的職責(zé)相當(dāng),但行為模式不同則不能成為超類和子類的關(guān)系(比如"著名"的正方形不是長(zhǎng)方形問(wèn)題)。馬類,作為超類,基于一些特殊的行為方式:吃草,跑...,對(duì)于白馬她的行為模式和馬是一樣的,并沒(méi)有不同,所以白馬是馬而非馬的同根繼承子類。對(duì)于長(zhǎng)方形和正方形都是具有相同計(jì)算面積算法(職責(zé))的四方形的子類。

          -------以下是關(guān)于此事的一些擴(kuò)展分析-----------
          3、子類的產(chǎn)生:
              何時(shí)需要產(chǎn)生子類呢:是對(duì)其父類的職責(zé)進(jìn)行擴(kuò)展,白馬沒(méi)有對(duì)父類的職責(zé)進(jìn)行擴(kuò)展,所以不是馬的子類。首先子類要擴(kuò)展超類,其次子類不能重寫或廢除超類的職責(zé)。

          4、屬性,狀態(tài)的區(qū)別(類的域)
             對(duì)于一些類,在狀態(tài)不同時(shí),會(huì)有不同的表現(xiàn)(狀態(tài)機(jī)模式),所以,類的getter,setter的部分包含兩種不同的特性,對(duì)于屬于狀態(tài)的部分,是我們要仔細(xì)分析的,而"白"馬則屬于屬性類(非狀態(tài))的域, 一般來(lái)講,一個(gè)類的實(shí)例要能提供相應(yīng)的差異服務(wù)(由于狀態(tài)不同)最好使用不變模式[生存周期狀態(tài)不變]或狀態(tài)機(jī)[生存周期有狀態(tài),但狀態(tài)不由調(diào)用者控制]來(lái)實(shí)現(xiàn)。

          5、抽象類和接口
             由于java的單根繼承特性,很多設(shè)計(jì)人員不敢定義抽象類為繼承樹(shù)根,一定要先定義馬的接口,在建立抽象馬,作為一種"準(zhǔn)規(guī)范"無(wú)可厚非,但我認(rèn)為這是不愿承擔(dān)責(zé)任的表現(xiàn),有行為的基類應(yīng)該可以(必須?)從類定義開(kāi)始,避免白馬類(一旦馬成為接口,白馬的產(chǎn)生就更加"名正言順"了)的出現(xiàn).將來(lái)如果發(fā)生變化可以通過(guò)重構(gòu)(導(dǎo)出接口和使用委托),解決問(wèn)題。

          6、對(duì)象的創(chuàng)建(組裝)和使用應(yīng)該分開(kāi)
             既然對(duì)象的狀態(tài)如此重要,屬性有有很大程度的不變性(白馬在構(gòu)造時(shí)就用該是白的,并且一生不變),而騎馬的人不必要求馬的屬性(!),所以,我們應(yīng)該將馬的構(gòu)造和使用分開(kāi),使領(lǐng)域模型更清晰。使用一些Ioc容器,比如Spring就能很好的解決這些問(wèn)題。
           
          7、分析問(wèn)題的領(lǐng)域
             說(shuō)了這么多,有一個(gè)問(wèn)題;如果有一個(gè)馬的研究機(jī)構(gòu),專門對(duì)不同顏色的馬進(jìn)行專題研究,馬的顏色可能會(huì)對(duì)馬的行為有很大影響,例如戰(zhàn)馬如果是黃色(綠色,哈哈)更利于偽裝,此時(shí)"白"可能是一個(gè)很關(guān)鍵的問(wèn)題,顏色會(huì)影響到不同的偽裝策略,此時(shí)將白馬作為馬的一個(gè)子類則是必須的!所以問(wèn)題域不同,類的設(shè)計(jì)就不同,生活中的問(wèn)題域比較清晰(生物學(xué)家和廚師對(duì)馬的理解不同),而軟件建模時(shí)往往問(wèn)題域混雜,這也是OO設(shè)計(jì)時(shí)比較困難的問(wèn)題,所以分析問(wèn)題域也是非常重要的設(shè)計(jì)問(wèn)題。

          posted @ 2006-01-19 14:16 兼聽(tīng)則明 閱讀(1068) | 評(píng)論 (4)編輯 收藏

          我來(lái)也

          過(guò)去一直在donews混跡,聽(tīng)朋友推薦,此處高手云集,我也來(lái)湊湊熱鬧。:-)

          posted @ 2006-01-19 14:05 兼聽(tīng)則明 閱讀(334) | 評(píng)論 (1)編輯 收藏

          主站蜘蛛池模板: 会泽县| 崇义县| 沾益县| 高安市| 公安县| 资中县| 遂昌县| 蛟河市| 天峨县| 绿春县| 泰州市| 额敏县| 公主岭市| 仁布县| 土默特左旗| 曲沃县| 恩平市| 安西县| 永吉县| 通山县| 邵阳市| 花莲市| 浙江省| 共和县| 广河县| 高雄县| 苍溪县| 静宁县| 竹北市| 蓬安县| 全州县| 兴安盟| 嵩明县| 婺源县| 新平| 英德市| 青海省| 祁门县| 叶城县| 瓦房店市| 乐山市|