<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    隨筆-14  評論-25  文章-1  trackbacks-0
      2014年6月21日
    在一個項目里面有這么一個技術需求:
    1.集合中元素個數,10M
    2.根據上限和下限從一個Set中過濾出滿足要求的元素集合.

    實際這個是個很典型的技術要求, 之前的項目也遇見過,但是因為當時的類庫不多, 都是直接手寫實現的. 方式基本等同于第一個方式.

    在這個過程中, 我寫了四個方式, 基本記錄到下面.
    第一個方式:對Set進行迭代器遍歷, 判斷每個元素是否都在上限和下限范圍中.如果滿足則添加到結果集合中, 最后返回結果集合.
                測試效果:集合大小100K, 運算時間 3000ms+
    過濾部分的邏輯如下:
     1     void filerSet(Set<BigDecimal> targetSet, String lower, String higher) {
     2         BigDecimal bdLower = new BigDecimal(Double.parseDouble(lower));
     3         BigDecimal bdHigher = new BigDecimal(Double.parseDouble(higher));
     4 
     5         Set<BigDecimal> returnSet = new HashSet<BigDecimal>();
     6         for (BigDecimal object : targetSet) {
     7             if (isInRange(object, bdLower, bdHigher)) {
     8                 returnSet.add(object);
     9             }
    10         }
    11     }
    12 
    13     private boolean isInRange(BigDecimal object, BigDecimal bdLower,
    14             BigDecimal bdHigher) {
    15         return object.compareTo(bdLower) >= 0
    16                 && object.compareTo(bdHigher) <= 0;
    17     }
    第二個方式: 借助TreeSet, 原始集合進行排序, 然后直接subset.
                測試效果: 集合大小10M, 運算時間: 12000ms+(獲得TreeSet) , 200ms(獲得結果)
    過濾部分的邏輯如下(非常繁瑣):
      1     Set<BigDecimal> getSubSet(TreeSet<BigDecimal> targetSet, String lower,
      2             String higher) {
      3 
      4         BigDecimal bdLower = new BigDecimal(Double.parseDouble(lower));
      5         BigDecimal bdHigher = new BigDecimal(Double.parseDouble(higher));
      6 
      7         if ((bdHigher.compareTo(targetSet.first()) == -1)
      8                 || (bdLower.compareTo(targetSet.last()) == 1)) {
      9             return null;
     10         }
     11 
     12         boolean hasLower = targetSet.contains(bdLower);
     13         boolean hasHigher = targetSet.contains(bdHigher);
     14         if (hasLower) {
     15             if (hasHigher) {
     16                 System.out.println("get start:" + bdLower);
     17                 System.out.println("get end:" + bdHigher);
     18                 return targetSet.subSet(bdLower, true, bdHigher, true);
     19             } else {
     20                 BigDecimal newEnd = null;
     21                 System.out.println("get start:" + bdLower);
     22                 SortedSet<BigDecimal> returnSet = null;
     23                 if (bdHigher.compareTo(targetSet.last()) != -1) {
     24                     newEnd = targetSet.last();
     25                 } else {
     26                     SortedSet<BigDecimal> newTargetSet = targetSet
     27                             .tailSet(bdLower);
     28                     for (BigDecimal object : newTargetSet) {
     29                         if (object.compareTo(bdHigher) == 1) {
     30                             newEnd = object;
     31                             break;
     32                         } else if (object.compareTo(bdHigher) == 0) {
     33                             newEnd = object;
     34                             break;
     35                         }
     36                     }
     37                 }
     38                 returnSet = targetSet.subSet(bdLower, true, newEnd, true);
     39                 if (newEnd.compareTo(bdHigher) == 1) {
     40                     returnSet.remove(newEnd);
     41                 }
     42                 return returnSet;
     43             }
     44 
     45         } else {
     46             if (hasHigher) {
     47                 System.out.println("get end:" + bdHigher);
     48                 TreeSet<BigDecimal> newTargetSet = (TreeSet<BigDecimal>) targetSet
     49                         .headSet(bdHigher, true);
     50                 BigDecimal newStart = null;
     51                 SortedSet<BigDecimal> returnSet = null;
     52 
     53                 if (bdLower.compareTo(targetSet.first()) == -1) {
     54                     newStart = targetSet.first();
     55                 } else {
     56                     for (BigDecimal object : newTargetSet) {
     57                         if (object.compareTo(bdLower) != -1) {
     58                             newStart = object;
     59                             break;
     60                         }
     61                     }
     62                 }
     63                 returnSet = targetSet.subSet(newStart, true, bdHigher, true);
     64 
     65                 return returnSet;
     66             } else {
     67                 System.out.println("Not get start:" + bdLower);
     68                 System.out.println("Not get end:" + bdHigher);
     69                 BigDecimal newStart = null;
     70                 BigDecimal newEnd = null;
     71                 if (bdHigher.compareTo(targetSet.last()) != -1) {
     72                     newEnd = targetSet.last();
     73                 }
     74                 if (bdLower.compareTo(targetSet.first()) == -1) {
     75                     newStart = targetSet.first();
     76                 }
     77                 for (BigDecimal object : targetSet) {
     78                     if (newStart == null) {
     79                         if (object.compareTo(bdLower) != -1) {
     80                             newStart = object;
     81                             if (newEnd != null) {
     82                                 break;
     83                             }
     84                         }
     85                     }
     86 
     87                     if (newEnd == null) {
     88                         if (object.compareTo(bdHigher) != -1) {
     89                             newEnd = object;
     90                             if (newStart != null) {
     91                                 break;
     92                             }
     93                         }
     94                     }
     95                 }
     96 
     97                 if (newStart == null) {
     98                     if (newEnd == null) {
     99                         if ((bdHigher.compareTo(targetSet.first()) == -1)
    100                                 || (bdLower.compareTo(targetSet.last()) == 1)) {
    101                             return null;
    102                         }
    103                         return targetSet;
    104                     } else {
    105                         SortedSet<BigDecimal> newTargetSet = targetSet.headSet(
    106                                 newEnd, true);
    107                         if (newEnd.compareTo(bdHigher) == 1) {
    108                             newTargetSet.remove(newEnd);
    109                         }
    110                         return newTargetSet;
    111                     }
    112                 } else {
    113                     if (newEnd == null) {
    114                         SortedSet<BigDecimal> newTargetSet = targetSet.tailSet(
    115                                 newStart, true);
    116                         return newTargetSet;
    117                     } else {
    118                         SortedSet<BigDecimal> newTargetSet = targetSet.subSet(
    119                                 newStart, true, newEnd, true);
    120                         if (newEnd.compareTo(bdHigher) == 1) {
    121                             newTargetSet.remove(newEnd);
    122                         }
    123                         return newTargetSet;
    124                     }
    125                 }
    126             }
    127         }
    128     }
    第三種方式: 使用Apache Commons Collections, 直接對于原始Set進行filter.
                測試效果:集合大小10M,過濾結果1M, 運算時間: 1000ms+
    過濾部分的代碼如下:
     1 //過濾的主體邏輯
     2     void filterSet(Set<BigDecimal> targetSet, String lower, String higher) {
     3         final BigDecimal bdLower = new BigDecimal(Double.parseDouble(lower));
     4         final BigDecimal bdHigher = new BigDecimal(Double.parseDouble(higher));
     5 
     6         Predicate predicate = new Predicate() {
     7             public boolean evaluate(Object object) {
     8                 BigDecimal bDObject = (BigDecimal) object;
     9                 return bDObject.compareTo(bdLower) >= 0
    10                         && bDObject.compareTo(bdHigher) <= 0;
    11             }
    12         };
    13 
    14         CollectionUtils.filter(targetSet, predicate);
    15     }

    第四種方式:使用Guava(google Collections), 直接對于原始Set進行Filter
                測試效果:集合大小10M,過濾結果1M, 運算時間: 100ms-
    過濾部分的代碼如下:
     1 //guava filter
     2 
     3     Set<BigDecimal> filterSet(Set<BigDecimal> targetSet, String lower,
     4             String higher) {
     5         final BigDecimal bdLower = new BigDecimal(Double.parseDouble(lower));
     6         final BigDecimal bdHigher = new BigDecimal(Double.parseDouble(higher));
     7 
     8         Set<BigDecimal> filterCollection = Sets.filter(targetSet,
     9                 new Predicate<BigDecimal>() {
    10                     @Override
    11                     public boolean apply(BigDecimal input) {
    12                         BigDecimal bDObject = (BigDecimal) input;
    13                         return bDObject.compareTo(bdLower) >= 0
    14                                 && bDObject.compareTo(bdHigher) <= 0;
    15                     }
    16                 });
    17 
    18         return filterCollection;
    19     }


    四種方式對比如下:
    第一種方式:  僅依賴于JAVA原生類庫 遍歷時間最慢, 代碼量很小
    第二種方式:  僅依賴于JAVA原生類庫 遍歷時間比較慢(主要慢在生成有序Set), 代碼量最多
    第三種方式:  依賴于Apache Commons Collections, 遍歷時間比較快, 代碼量很少
    第四種方式:  依賴于Guava, 遍歷時間最快, 代碼量很少

    基于目前個人的技術水平和視野, 第四種方式可能是最佳選擇.

    記錄一下, 以后可能還會有更好的方案.




    posted @ 2014-06-21 23:33 混沌中立 閱讀(7370) | 評論 (10)編輯 收藏
      2009年9月2日
    在幾年之前,在大學里面的時候,認為系統的架構設計,就像建筑設計一樣,會把骨架搭成,然后有具體人員進行詳細的開發.

    在后來,工作中,慢慢有了一些變化,因為原先的想法不太切合實際,系統就是在變化之中的,如果固定了骨架,那就很難的敏捷面對變化.
    所以,系統的架構設計,應該是面向接口的設計,確定各個部分之間的數據接口和方法接口.這樣,即使有了變化,只要遵循接口的定義,就還是可以面對變化.


    最近,又有了想法的變化.架構的設計,應該就是規則和規約的設計.設計出一系列,統一的,和諧的規則,在這些規則之前圈住的部分,實際就是系統的全貌.
    接口的設計,實際只是規則和規約設計的一個部分.
    架構的設計,不應該只是程序方面的事情,同時也包含了心理學方面和社會學方面的一些規則.例如:團隊在面對變化時候,需要采用的規則和流程.
    只有包含了這些非程序上的規則之后,才能保證架構風格的統一協調.


    以上,是我對系統設計的想法的轉變過程.記錄于此,以供回溯.




    posted @ 2009-09-02 10:53 混沌中立 閱讀(247) | 評論 (0)編輯 收藏
      2009年8月20日
    面對著滿屏幕的程序
    是三年前,項目剛剛啟動的時候,同事寫的代碼.
    三年過去了,項目由第一期變成了第七期.

    這段代碼還是在這里,有個屬性是list,其中每個cell都是一個長度18的String數組.
    數組里面放置了所需要導出到頁面table的內容.

    現在要開始修改了,需要向頁面的table中增加4列.
    繁瑣的讓人要命的工作,需要跟蹤這個循環,判斷每個pattern下面,這個長度18的數組里面放了哪些內容.

    好吧,對象化維護從數組開始,把數組對折,因為這個數組時一個比較數組,前面9個元素是之前的情況,后面9個事之后的情況.
    用一個bean,放入兩次就可以了.但是bean中,需要一個標志,標識是之前的情況還是之后的情況.

    同時需要一個transform方法,把之前從幾個來源過來的情況,變成bean的屬性.
    接下來需要一個values方法,把bean里面的屬性直接按順序轉化成數組.
    本期新增的4個屬性,直接放入bean中就可以了.

    這樣原來很復雜的數組,就可以簡單的用對象來解決.外部的接口完全沒有變化.

    維護程序,從把數組(特別是異型數組)對象化開始.

    posted @ 2009-08-20 13:43 混沌中立 閱讀(1353) | 評論 (1)編輯 收藏
      2006年7月13日
    這個小的project是前一個階段,待業在家的時候,迷戀sudoku的時候,自己寫來玩的。
    正好當時在看Uncle Bob的《Agile Software Development: Principles, Patterns, and Practices》 (敏捷軟件開發:原則、模式與實踐),所以就按照自己對書中的一些概念和方法的理解,結合自己之前的開發經驗寫出來一段小的代碼。

    代碼行數: < 900
    類的個數: 18
    抽象類的個數:2
    工廠類的個數:1
    包的個數:5

    一些關于類和包作用的說明:
    1.Cell:表示一個Cell,是一個游戲中的一個單元格。
    ? Cell主要由3個部分組成,Point,Value,Status.
    2.Point:表示一個坐標,主要格式為:(2,3).
    ? ?。。∽⒁猓河捎趥€人比較懶,所以開始的錯誤被貫徹了下來。
    ? 這個錯誤就是(2,3)表示的是由最左上的位置為坐標原點,第二行和第三列所確定的那個單元格。也就是縱坐標在前,橫坐標在后了。
    3.Value:表示一個值
    4.Status:表示Cell的狀態,只有兩個狀態,一個是NotSure,另一個是Sure.

    5.AbstractCells:表示一些cell的集合,主要有三個子類
    ???? BlockCells:表示一個由多個Cell組成的塊,例如一個2*2由4個Cell組成的塊,或者一個2*3由6個Cell組成的塊
    ???? HorizonCells:表示一個橫行,即:從(0,0)到(0,n)坐標確定的所有Cell的集合。
    ???? VerticalCells:表示一個縱行,即:從(0,0)到(n,0)坐標確定的所有Cell的集合。
    6.AbstractPolicy:就是游戲的策略。
    ?? 這個主要表示的是:4*4的游戲,還是9*9的游戲。
    ?? 可以在以后對此類進行繼承和擴展,例如16*16的游戲我就沒有實現。
    ?? 主要擴展3個方法:
    ????????????????? 1)getValueRange,返回當前policy的value的個數。4*4的游戲的getValueRange返回的就應該是4。
    ??? ??? ? 2)getStep:表示當前policy中相鄰的兩個BlockCells的坐標差。
    ??? ??? ? 3)getIncrease:說不明白了:)(只可意會不可言傳。)
    7.Game:進行Policy的場所(我一直想拋棄這個類)
    8.TestGame:游戲運行的地方,包括從PolicyFactory取得指定的Policy,設置輸入輸出文件的路徑。
    9.PolicyFactory:取得Policy的工廠。
    ??? getPolicy(int x) :這個方法獲得的是正方形的sudoku的策略。例如:4*4的,9*9,16*16。
    ??? getPolicy(int x, int y):這個方法獲得的是長方形的Sudoku的策略。例如:9*12的。


    雖然是盡量避免bad code smell,但是由于能力有限,還是出現了一些不好的地方。
    例如:之間的關聯關系還是很多,而且很強;抽象的方法和抽象類的個數偏少等等。

    里面實現了三個解決sudoku的方法:
    1.在一個Cell中出現的Value,不會在和這個Cell處在同一個AbstractCells中的所有Cell中出現;
    2.如果一個Cell中,所有可能出現的Value的個數為1,那么Cell的Value必然是這個最后的Value;
    2.如果一個Value,如果在當前AbstractCells的所有其他的Cell中都不可能出現,那么它必然是最后一個Cell的Value。

    附件1:src code
    http://www.tkk7.com/Files/GandofYan/sudoku.rar
    附件2:輸入輸出文件的example
    http://www.tkk7.com/Files/GandofYan/temp.rar

    posted @ 2006-07-13 16:19 混沌中立 閱讀(2162) | 評論 (4)編輯 收藏
      2006年6月7日
    如同Tom DeMacro說的:無法控制的東西就不能管理,無法測量的東西就無法控制。
    軟件的度量對于設計者和開發者非常重要,之前只是對這些有一個簡單的了解。今天看來,了解的還遠遠不夠。
    • Cyclomatic Complexity (圈復雜性)
    • Response for Class (類的響應)
    • Weighted methods per class (每個類重量方法)
    一個系統中的所有類的這三個度量能夠說明這個系統的設計上的一些問題(不是全部),這三個度量越大越不好。
    如果一個類這三個度量很高,證明了這個類需要重構了。

    以第一個度量來說,有下面的一個表格:

    CC Value

    Risk

    1-10

    Low risk program

    11-20

    Moderate risk

    21-50

    High risk

    >50

    Most complex and highly unstable method


    CC數值高,可以通過減少if else(switch case也算)判斷來達到目的;
    可以通過減少類與其他類的調用來減少RFC;
    通過分割大方法和大類來達到減少WMPC.

    而Uncle Bob和Jdepend的度量標準應該算是另一個度量系統。

    • 關系內聚性(H)
    用包中的每個類平均的內部關系數目作為包內聚性的一種表示方式。用于表示包和它的所有類之間的關系。
    H=(R+1)/N
    R:包內類的關系數目(與包外部的類沒有關系)
    N:包內類的數量

    • Number of Classes (Cc)
    被分析package的具體和抽象類(和接口)的數量,用于衡量package的可擴展性。

    • Afferent Couplings (Ca)
    依賴于被分析package的其他package的數量,用于衡量pacakge的職責。
    • Efferent Couplings (Ce)
    被分析package的類所依賴的其他package的數量,用于衡量package的獨立性。
    • Abstractness (A)
    被分析package中的抽象類和接口與所在package所有類數量的比例,取值范圍為0-1。
    A=Cc/N
    • Instability (I)
    用于衡量package的不穩定性,取值范圍為0-1。I=0表示最穩定,I=1表示最不穩定。
    I=Ce/(Ce+Ca)
    • Distance (D)
    ??? ??? ? 被分析package和理想曲線A+I=1的垂直距離,用于衡量package在穩定性和抽象性之間的平衡。理想??? ??? ? 的package要么完全是抽象類和穩定(x=0,y=1),要么完全是具體類和不穩定(x=1,y=0)。
    ??? ??? ? 取值范圍為0-1,D=0表示完全符合理想標準,D=1表示package最大程度地偏離了理想標準。
    ??? ?? ?? D = |A+I-1|/0.70710678
    ??? ?? ?? 注:0.70710678*0.70710678 =2,既為“根號2“

    我認為D是一個綜合的度量,架構和設計的改善可以通過D數值的減少來體現,反之就可以認為是設計和架構的退化。


    讀過http://javaboutique.internet.com/tutorials/metrics/index.html之后的一些想法

    另一篇中文的內容相近的文章可以參考http://www.jdon.com/artichect/coupling.htm

    不過第二篇的中文文章中間關于Cyclomatic Complexity,有一個情況遺漏了
    public void findApplications(String id, String name){

    if(id!=null && name!=null) {
    //do something
    }else{
    //do something
    }
    }
    這種情況的CC不是2+1,而是2+1+1,依據是公式(1)。公式(2)應該是公式(1)的簡化版。
    Cyclomatic ComplexityCC) = no of decision points + no of logical operations +1        (1)

    Cyclomatic Complexity (CC) = number of decision points +1 (2)

    參考了JDepend的參數和Uncle Bob的《
    Agile Software Development: Principles, Patterns, and Practices(敏捷軟件開發:原則、模式與實踐)
    posted @ 2006-06-07 10:52 混沌中立 閱讀(1511) | 評論 (3)編輯 收藏
      2006年5月30日
    轉自:http://www.keyusoft.cn/Contentview.aspx?year=2005&month=$10&day=$6&postid=123

    通過一周左右的研究,對規則引擎有了一定的了解?,F在寫點東西跟大家一起交流,本文主要針對RETE算法進行描述。我的文筆不太好,如果有什么沒講明白的或是說錯的地方,請給我留言。
    首先申明,我的帖子借鑒了網上很流行的一篇帖子,好像是來自CSDN;還有一點,我不想做太多的名詞解釋,因為我也不是個研究很深的人,定義的不好怕被笑話。
    好現在我們開始。
    首先介紹一些網上對于規則引擎比較好的帖子。
    1、 來自JAVA視頻網
    http://forum.javaeye.com/viewtopic.php?t=7803&postdays=0&postorder=asc&start=0
    2、? RETE算法的最原始的描述,我不知道在哪里找到的,想要的人可以留下E-mail
    3、? CMU的一位博士生的畢業論文,個人覺得非常好,我的很多觀點都是來自這里的,要的人也可以給我發mail? ?mailto:ipointer@163.com
    ?
    接著統一一下術語,很多資料里的術語都非常混亂。
    1、? facts 事實,我們實現的時候,會有一個事實庫。用F表示。
    2、? patterns 模板,事實的一個模型,所有事實庫中的事實都必須滿足模板中的一個。用P表示。
    3、 ? conditions 條件,規則的組成部分。也必須滿足模板庫中的一條模板。用C表示。我們可以這樣理解facts、patterns、conditions之間的關系。 Patterns是一個接口,conditions則是實現這個接口的類,而facts是這個類的實例。
    4、? rules 規則,由一到多個條件構成。一般用and或or連接conditions。用R表示。
    5、? actions 動作,激活一條rule執行的動作。我們這里不作討論。
    6、? 還有一些術語,如:working-memory、production-memory,跟這里的概念大同小異。
    7、? 還有一些,如:alpha-network、beta-network、join-node,我們下面會用到,先放一下,一會討論。
    ?
    引用一下網上很流行的例子,我覺得沒講明白,我在用我的想法解釋一下。
    ?
    假設在規則記憶中有下列三條規則
    ?
    if A(x) and B(x) and C(y) then add D(x)
    if A(x) and B(y) and D(x) then add E(x)
    if A(x) and B(x) and E(x) then delete A(x)
    ?
    RETE算法會先將規則編譯成下列的樹狀架構排序網絡

    而工作記憶內容及順序為{A(1),A(2),B(2),B(3),B(4),C(5)},當工作記憶依序進入網絡后,會依序儲存在符合條件的節點中,直到完全符合條件的推論規則推出推論。以上述例子而言, 最后推得D(2)。
    ?
    讓我們來分析這個例子。
    ?
    模板庫:(這個例子中只有一個模板,算法原描述中有不同的例子, 一般我們會用tuple,元組的形式來定義facts,patterns,condition)
    P: (?A , ?x)? 其中的A可能代表一定的操作,如例子中的A,B,C,D,E ; x代表操作的參數??纯催@個模板是不是已經可以描述所有的事實。
    ?
    條件庫:(這里元組的第一項代表實際的操作,第二項代表形參)
    C1: (A , <x>)
    C2: (B , <x>)
    C3: (C , <y>)
    C4: (D , <x>)
    C5: (E , <x>)
    C6: (B , <y>)
    ?
    事實庫:(第二項代表實參)
    F1: (A,1)
    F2: (A,2)
    F3: (B,2)
    F4: (B,3)
    F5: (B,4)
    F6: (C,5)
    ?
    ?????? 規則庫:
    ?????? ? R1: c1^c2^c3
    ?????? ? R2: c1^c2^c4
    ?????? ? R3: c1^c2^c5
    ?
    ??????
    ?????? 有人可能會質疑R1: c1^c2^c3,沒有描述出,原式中:
    if A(x) and B(x) and C(y) then add D(x),A=B的關系。但請仔細看一下,這一點已經在條件庫中定義出來了。
    ?
    ?????? 下面我來描述一下,規則引擎中RETE算法的實現。
    ?????? 首先,我們要定一些規則,根據這些規則,我們的引擎可以編譯出一個樹狀結構,上面的那張圖中是一種簡易的表現,其實在實現的時候不是這個樣子的。
    ?????? 這就是beta-network出場的時候了,根據rules我們就可以確定beta-network,下面,我就畫出本例中的beta-network,為了描述方便,我把alpha-network也畫出來了。
    ??????
    ?
    上圖中,左邊的部分就是beta-network,右邊是alpha-network,圓圈是join-node.
    從上圖中,我們可以驗證,在beta-network中,表現出了rules的內容,其中r1,r2,r3共享了許多BM和join-node,這是由于這些規則中有共同的部分,這樣能加快match的速度。
    右 邊的alpha-network是根據事實庫構建的,其中除alpha-network節點的節點都是根據每一條condition,從事實庫中 match過來的,這一過程是靜態的,即在編譯構建網絡的過程中已經建立的。只要事實庫是穩定的,即沒有大幅度的變化,RETE算法的執行效率應該是非常 高的,其原因就是已經通過靜態的編譯,構建了alpha-network。我們可以驗證一下,滿足c1的事實確實是w1,w2。
    下 面我們就看一下,這個算法是怎么來運行的,即怎么來確定被激活的rules的。從top-node往下遍歷,到一個join-node,與AM for c1的節點匯合,運行到match c1節點。此時,match c1節點的內容就是:w1,w2。繼續往下,與AM for c2匯合(所有可能的組合應該是w1^w3,w1^w4,w1^w5,w2^w3,w2^w4,w2^w5),因為c1^c2要求參數相同,因此, match c1^c2的內容是:w2^w3。再繼續,這里有一個扇出(fan-out),其中只有一個join-node可以被激活,因為旁邊的AM只有一個非空。 因此,也只有R1被激活了。
    解決扇出帶來的效率降低的問題,我們可以使用hashtable來解決這個問題。
    RETE算法還有一些問題,如:facts庫變化,我們怎么才能高效的重建alpha-network,同理包括rules的變化對beta-network的影響。這一部分我還沒細看,到時候再貼出來吧。
    posted @ 2006-05-30 15:30 混沌中立 閱讀(1047) | 評論 (2)編輯 收藏
    最近插件又加多了,eclipse老是死掉
    ?
    一怒之下,刪除重裝
    ?
    以前因為懶,沒有把插件的目錄和主體的目錄分開,這次也給它分開了
    ?
    ?
    插件少了之后,eclipse確實快了不少
    ?
    附eclipse啟動參數: -nl en_US vmargs -Xverify:none -Xms256M -Xmx1024M -XX:PermSize=50M? -XX:+UseParallelGC
    posted @ 2006-05-30 13:48 混沌中立 閱讀(568) | 評論 (0)編輯 收藏
    freemind一個比較不錯free的 mind map 軟件,很多人建議使用這個來管理自己的思路.
    ?
    mind manager另一個比較不錯的mind map的軟件,可以和office兼容.不過是商業的
    ?
    visio,就不介紹了.office里面有的東西.做流程圖來說,確實是比較好的軟件.但是在思路不清楚的時候,很難畫出什么有用的東西來.這點就比不上前面兩個東西了.不過對我來說visio可能更順手,因為我經常畫的是軟件流程圖........
    posted @ 2006-05-30 13:36 混沌中立 閱讀(1754) | 評論 (2)編輯 收藏
    總結一下最近關于domain object以及相關的討論
    ?
    在最近的圍繞domain object的討論中浮現出來了三種模型,(還有一些其他的旁枝,不一一分析了),經過一番討論,各種問題逐漸清晰起來,在這里我試圖做一個總結,便于大家了解和掌握。

    第一種模型:只有getter/setter方法的純數據類,所有的業務邏輯完全由business object來完成(又稱TransactionScript),這種模型下的domain object被Martin Fowler稱之為“貧血的domain object”。下面用舉一個具體的代碼來說明,代碼來自Hibernate的caveatemptor,但經過我的改寫:

    一個實體類叫做Item,指的是一個拍賣項目
    一個DAO接口類叫做ItemDao
    一個DAO接口實現類叫做ItemDaoHibernateImpl
    一個業務邏輯類叫做ItemManager(或者叫做ItemService)

    java代碼:?

    public class Item implementsSerializable{
    ? ? privateLong id = null;
    ? ? privateint version;
    ? ? privateString name;
    ? ? private User seller;
    ? ? privateString description;
    ? ? private MonetaryAmount initialPrice;
    ? ? private MonetaryAmount reservePrice;
    ? ? privateDate startDate;
    ? ? privateDate endDate;
    ? ? privateSet categorizedItems = newHashSet();
    ? ? privateCollection bids = newArrayList();
    ? ? private Bid successfulBid;
    ? ? private ItemState state;
    ? ? private User approvedBy;
    ? ? privateDate approvalDatetime;
    ? ? privateDate created = newDate();
    ? ? //? getter/setter方法省略不寫,避免篇幅太長
    }



    java代碼:?

    public interface ItemDao {
    ? ? public Item getItemById(Long id);
    ? ? publicCollection findAll();
    ? ? publicvoid updateItem(Item item);
    }



    ItemDao定義持久化操作的接口,用于隔離持久化代碼。

    java代碼:?

    public class ItemDaoHibernateImpl implements ItemDao extends HibernateDaoSupport {
    ? ? public Item getItemById(Long id){
    ? ? ? ? return(Item) getHibernateTemplate().load(Item.class, id);
    ? ? }
    ? ? publicCollection findAll(){
    ? ? ? ? return(List) getHibernateTemplate().find("from Item");
    ? ? }
    ? ? publicvoid updateItem(Item item){
    ? ? ? ? getHibernateTemplate().update(item);
    ? ? }
    }


    ItemDaoHibernateImpl完成具體的持久化工作,請注意,數據庫資源的獲取和釋放是在ItemDaoHibernateImpl 里面處理的,每個DAO方法調用之前打開Session,DAO方法調用之后,關閉Session。(Session放在ThreadLocal中,保證一次調用只打開關閉一次)

    java代碼:?

    public class ItemManager {
    ? ? private ItemDao itemDao;
    ? ? publicvoid setItemDao(ItemDao itemDao){ this.itemDao = itemDao;}
    ? ? public Bid loadItemById(Long id){
    ? ? ? ? itemDao.loadItemById(id);
    ? ? }
    ? ? publicCollection listAllItems(){
    ? ? ? ? return? itemDao.findAll();
    ? ? }
    ? ? public Bid placeBid(Item item, User bidder, MonetaryAmount bidAmount,
    ? ? ? ? ? ? ? ? ? ? ? ? ? ? Bid currentMaxBid, Bid currentMinBid)throws BusinessException {
    ? ? ? ? ? ? if(currentMaxBid != null && currentMaxBid.getAmount().compareTo(bidAmount) > 0){
    ? ? ? ? ? ? throw new BusinessException("Bid too low.");
    ? ? }
    ? ?
    ? ? // Auction is active
    ? ? if( !state.equals(ItemState.ACTIVE))
    ? ? ? ? ? ? throw new BusinessException("Auction is not active yet.");
    ? ?
    ? ? // Auction still valid
    ? ? if( item.getEndDate().before(newDate()))
    ? ? ? ? ? ? throw new BusinessException("Can't place new bid, auction already ended.");
    ? ?
    ? ? // Create new Bid
    ? ? Bid newBid = new Bid(bidAmount, item, bidder);
    ? ?
    ? ? // Place bid for this Item
    ? ? item.getBids().add(newBid);
    ? ? itemDao.update(item);? ? ?//? 調用DAO完成持久化操作
    ? ? return newBid;
    ? ? }
    }



    事務的管理是在ItemManger這一層完成的,ItemManager實現具體的業務邏輯。除了常見的和CRUD有關的簡單邏輯之外,這里還有一個placeBid的邏輯,即項目的競標。

    以上是一個完整的第一種模型的示例代碼。在這個示例中,placeBid,loadItemById,findAll等等業務邏輯統統放在ItemManager中實現,而Item只有getter/setter方法。

    ?

    ?

    第二種模型,也就是Martin Fowler指的rich domain object是下面這樣子的:

    一個帶有業務邏輯的實體類,即domain object是Item
    一個DAO接口ItemDao
    一個DAO實現ItemDaoHibernateImpl
    一個業務邏輯對象ItemManager

    java代碼:?

    public class Item implementsSerializable{
    ? ? //? 所有的屬性和getter/setter方法同上,省略
    ? ? public Bid placeBid(User bidder, MonetaryAmount bidAmount,
    ? ? ? ? ? ? ? ? ? ? ? ? Bid currentMaxBid, Bid currentMinBid)
    ? ? ? ? ? ? throws BusinessException {
    ? ?
    ? ? ? ? ? ? // Check highest bid (can also be a different Strategy (pattern))
    ? ? ? ? ? ? if(currentMaxBid != null && currentMaxBid.getAmount().compareTo(bidAmount) > 0){
    ? ? ? ? ? ? ? ? ? ? throw new BusinessException("Bid too low.");
    ? ? ? ? ? ? }
    ? ?
    ? ? ? ? ? ? // Auction is active
    ? ? ? ? ? ? if( !state.equals(ItemState.ACTIVE))
    ? ? ? ? ? ? ? ? ? ? throw new BusinessException("Auction is not active yet.");
    ? ?
    ? ? ? ? ? ? // Auction still valid
    ? ? ? ? ? ? if( this.getEndDate().before(newDate()))
    ? ? ? ? ? ? ? ? ? ? throw new BusinessException("Can't place new bid, auction already ended.");
    ? ?
    ? ? ? ? ? ? // Create new Bid
    ? ? ? ? ? ? Bid newBid = new Bid(bidAmount, this, bidder);
    ? ?
    ? ? ? ? ? ? // Place bid for this Item
    ? ? ? ? ? ? this.getBids.add(newBid);? // 請注意這一句,透明的進行了持久化,但是不能在這里調用ItemDao,Item不能對ItemDao產生依賴!
    ? ?
    ? ? ? ? ? ? return newBid;
    ? ? }
    }



    競標這個業務邏輯被放入到Item中來。請注意this.getBids.add(newBid); 如果沒有Hibernate或者JDO這種O/R Mapping的支持,我們是無法實現這種透明的持久化行為的。但是請注意,Item里面不能去調用ItemDAO,對ItemDAO產生依賴!

    ItemDao和ItemDaoHibernateImpl的代碼同上,省略。

    java代碼:?

    public class ItemManager {
    ? ? private ItemDao itemDao;
    ? ? publicvoid setItemDao(ItemDao itemDao){ this.itemDao = itemDao;}
    ? ? public Bid loadItemById(Long id){
    ? ? ? ? itemDao.loadItemById(id);
    ? ? }
    ? ? publicCollection listAllItems(){
    ? ? ? ? return? itemDao.findAll();
    ? ? }
    ? ? public Bid placeBid(Item item, User bidder, MonetaryAmount bidAmount,
    ? ? ? ? ? ? ? ? ? ? ? ? ? ? Bid currentMaxBid, Bid currentMinBid)throws BusinessException {
    ? ? ? ? item.placeBid(bidder, bidAmount, currentMaxBid, currentMinBid);
    ? ? ? ? itemDao.update(item);? ? // 必須顯式的調用DAO,保持持久化
    ? ? }
    }



    在第二種模型中,placeBid業務邏輯是放在Item中實現的,而loadItemById和findAll業務邏輯是放在 ItemManager中實現的。不過值得注意的是,即使placeBid業務邏輯放在Item中,你仍然需要在ItemManager中簡單的封裝一層,以保證對placeBid業務邏輯進行事務的管理和持久化的觸發。

    這種模型是Martin Fowler所指的真正的domain model。在這種模型中,有三個業務邏輯方法:placeBid,loadItemById和findAll,現在的問題是哪個邏輯應該放在Item 中,哪個邏輯應該放在ItemManager中。在我們這個例子中,placeBid放在Item中(但是ItemManager也需要對它進行簡單的封裝),loadItemById和findAll是放在ItemManager中的。

    切分的原則是什么呢? Rod Johnson提出原則是“case by case”,可重用度高的,和domain object狀態密切關聯的放在Item中,可重用度低的,和domain object狀態沒有密切關聯的放在ItemManager中。

    我提出的原則是:看業務方法是否顯式的依賴持久化。

    Item的placeBid這個業務邏輯方法沒有顯式的對持久化ItemDao接口產生依賴,所以要放在Item中。請注意,如果脫離了Hibernate這個持久化框架,Item這個domain object是可以進行單元測試的,他不依賴于Hibernate的持久化機制。它是一個獨立的,可移植的,完整的,自包含的域對象

    而loadItemById和findAll這兩個業務邏輯方法是必須顯式的對持久化ItemDao接口產生依賴,否則這個業務邏輯就無法完成。如果你要把這兩個方法放在Item中,那么Item就無法脫離Hibernate框架,無法在Hibernate框架之外獨立存在。

    ?

    ?

    第三種模型印象中好像是firebody或者是Archie提出的(也有可能不是,記不清楚了),簡單的來說,這種模型就是把第二種模型的domain object和business object合二為一了。所以ItemManager就不需要了,在這種模型下面,只有三個類,他們分別是:

    Item:包含了實體類信息,也包含了所有的業務邏輯
    ItemDao:持久化DAO接口類
    ItemDaoHibernateImpl:DAO接口的實現類

    由于ItemDao和ItemDaoHibernateImpl和上面完全相同,就省略了。

    java代碼:?

    public class Item implementsSerializable{
    ? ? //? 所有的屬性和getter/setter方法都省略
    ? ?privatestatic ItemDao itemDao;
    ? ? publicvoid setItemDao(ItemDao itemDao){this.itemDao = itemDao;}
    ? ?
    ? ? publicstatic Item loadItemById(Long id){
    ? ? ? ? return(Item) itemDao.loadItemById(id);
    ? ? }
    ? ? publicstaticCollection findAll(){
    ? ? ? ? return(List) itemDao.findAll();
    ? ? }

    ? ? public Bid placeBid(User bidder, MonetaryAmount bidAmount,
    ? ? ? ? ? ? ? ? ? ? Bid currentMaxBid, Bid currentMinBid)
    ? ? throws BusinessException {
    ? ?
    ? ? ? ? // Check highest bid (can also be a different Strategy (pattern))
    ? ? ? ? if(currentMaxBid != null && currentMaxBid.getAmount().compareTo(bidAmount) > 0){
    ? ? ? ? ? ? ? ? throw new BusinessException("Bid too low.");
    ? ? ? ? }
    ? ? ? ?
    ? ? ? ? // Auction is active
    ? ? ? ? if( !state.equals(ItemState.ACTIVE))
    ? ? ? ? ? ? ? ? throw new BusinessException("Auction is not active yet.");
    ? ? ? ?
    ? ? ? ? // Auction still valid
    ? ? ? ? if( this.getEndDate().before(newDate()))
    ? ? ? ? ? ? ? ? throw new BusinessException("Can't place new bid, auction already ended.");
    ? ? ? ?
    ? ? ? ? // Create new Bid
    ? ? ? ? Bid newBid = new Bid(bidAmount, this, bidder);
    ? ? ? ?
    ? ? ? ? // Place bid for this Item
    ? ? ? ? this.addBid(newBid);
    ? ? ? ? itemDao.update(this);? ? ? //? 調用DAO進行顯式持久化
    ? ? ? ? return newBid;
    ? ? }
    }



    在這種模型中,所有的業務邏輯全部都在Item中,事務管理也在Item中實現。

    ?

    ?

    在上面三種模型之外,還有很多這三種模型的變種,例如partech的模型就是把第二種模型中的DAO和 Manager三個類合并為一個類后形成的模型;例如frain....(id很長記不住)的模型就是把第三種模型的三個類完全合并為一個單類后形成的模型;例如Archie是把第三種模型的Item又分出來一些純數據類(可能是,不確定)形成的一個模型。

    但是不管怎么變,基本模型歸納起來就是上面的三種模型,下面分別簡單評價一下:

    第一種模型絕大多數人都反對,因此反對理由我也不多講了。但遺憾的是,我觀察到的實際情形是,很多使用Hibernate的公司最后都是這種模型,這里面有很大的原因是很多公司的技術水平沒有達到這種層次,所以導致了這種貧血模型的出現。從這一點來說,Martin Fowler的批評聲音不是太響了,而是太弱了,還需要再繼續吶喊。

    第二種模型就是Martin Fowler一直主張的模型,實際上也是我一直在實際項目中采用這種模型。我沒有看過Martin的POEAA,之所以能夠自己摸索到這種模型,也是因為從02年我已經開始思考這個問題并且尋求解決方案了,但是當時沒有看到Hibernate,那時候做的一個小型項目我已經按照這種模型來做了,但是由于沒有O/R Mapping的支持,寫到后來又不得不全部改成貧血的domain object,項目做完以后再繼續找,隨后就發現了Hibernate。當然,現在很多人一開始就是用Hibernate做項目,沒有經歷過我經歷的那個階段。

    不過我覺得這種模型仍然不夠完美,因為你還是需要一個業務邏輯層來封裝所有的domain logic,這顯得非常羅嗦,并且業務邏輯對象的接口也不夠穩定。如果不考慮業務邏輯對象的重用性的話(業務邏輯對象的可重用性也不可能好),很多人干脆就去掉了xxxManager這一層,在Web層的Action代碼直接調用xxxDao,同時容器事務管理配置到Action這一層上來。 Hibernate的caveatemptor就是這樣架構的一個典型應用。

    第三種模型是我很反對的一種模型,這種模型下面,Domain Object和DAO形成了雙向依賴關系,無法脫離框架測試,并且業務邏輯層的服務也和持久層對象的狀態耦合到了一起,會造成程序的高度的復雜性,很差的靈活性和糟糕的可維護性。也許將來技術進步導致的O/R Mapping管理下的domain object發展到足夠的動態持久透明化的話,這種模型才會成為一個理想的選擇。就像O/R Mapping的流行使得第二種模型成為了可能(O/R Mapping流行以前,我們只能用第一種模型,第二種模型那時候是不現實的)。

    ?

    ?

    既然大家都統一了觀點,那么就有了一個很好的討論問題的基礎了。Martin Fowler的Domain Model,或者說我們的第二種模型難道是完美無缺的嗎?當然不是,接下來我就要分析一下它的不足,以及可能的解決辦法,而這些都來源于我個人的實踐探索。

    在第二種模型中,我們可以清楚的把這4個類分為三層:

    1、實體類層,即Item,帶有domain logic的domain object
    2、DAO層,即ItemDao和ItemDaoHibernateImpl,抽象持久化操作的接口和實現類
    3、業務邏輯層,即ItemManager,接受容器事務控制,向Web層提供統一的服務調用

    在這三層中我們大家可以看到,domain object和DAO都是非常穩定的層,其實原因也很簡單,因為domain object是映射數據庫字段的,數據庫字段不會頻繁變動,所以domain object也相對穩定,而面向數據庫持久化編程的DAO層也不過就是CRUD而已,不會有更多的花樣,所以也很穩定。

    問題就在于這個充當business workflow facade的業務邏輯對象,它的變動是相當頻繁的。業務邏輯對象通常都是無狀態的、受事務控制的、Singleton類,我們可以考察一下業務邏輯對象都有哪幾類業務邏輯方法:

    第一類:DAO接口方法的代理,就是上面例子中的loadItemById方法和findAll方法。

    ItemManager之所以要代理這種類,目的有兩個:向Web層提供統一的服務調用入口點和給持久化方法增加事務控制功能。這兩點都很容易理解,你不能既給Web層程序員提供xxxManager,也給他提供xxxDao,所以你需要用xxxManager封裝xxxDao,在這里,充當了一個簡單代理功能;而事務控制也是持久化方法必須的,事務可能需要跨越多個DAO方法調用,所以必須放在業務邏輯層,而不能放在DAO層。

    但是必須看到,對于一個典型的web應用來說,絕大多數的業務邏輯都是簡單的CRUD邏輯,所以這種情況下,針對每個DAO方法,xxxManager都需要提供一個對應的封裝方法,這不但是非常枯燥的,也是令人感覺非常不好的。


    第二類:domain logic的方法代理。就是上面例子中placeBid方法。雖然Item已經有了placeBid方法,但是ItemManager仍然需要封裝一下Item的placeBid,然后再提供一個簡單封裝之后的代理方法。

    這和第一種情況類似,其原因也一樣,也是為了給Web層提供一個統一的服務調用入口點和給隱式的持久化動作提供事務控制。

    同樣,和第一種情況一樣,針對每個domain logic方法,xxxManager都需要提供一個對應的封裝方法,同樣是枯燥的,令人不爽的。


    第三類:需要多個domain object和DAO參與協作的business workflow。這種情況是業務邏輯對象真正應該完成的職責。

    在這個簡單的例子中,沒有涉及到這種情況,不過大家都可以想像的出來這種應用場景,因此不必舉例說明了。

    通過上面的分析可以看出,只有第三類業務邏輯方法才是業務邏輯對象真正應該承擔的職責,而前兩類業務邏輯方法都是“無奈之舉”,不得不為之的事情,不但枯燥,而且令人沮喪。




    分析完了業務邏輯對象,我們再回頭看一下domain object,我們要仔細考察一下domain logic的話,會發現domain logic也分為兩類:

    第一類:需要持久層框架隱式的實現透明持久化的domain logic,例如Item的placeBid方法中的這一句:

    java代碼:?

    this.getBids().add(newBid);


    上面已經著重提到,雖然這僅僅只是一個Java集合的添加新元素的操作,但是實際上通過事務的控制,會潛在的觸發兩條SQL:一條是insert一條記錄到bid表,一條是更新item表相應的記錄。如果我們讓Item脫離Hibernate進行單元測試,它就是一個單純的Java集合操作,如果我們把他加入到Hibernate框架中,他就會潛在的觸發兩條SQL,這就是隱式的依賴于持久化的domain logic
    特別請注意的一點是:在沒有Hibernate/JDO這類可以實現“透明的持久化”工具出現之前,這類domain logic是無法實現的。

    對于這一類domain logic,業務邏輯對象必須提供相應的封裝方法,以實現事務控制。


    第二類:完全不依賴持久化的domain logic,例如readonly例子中的Topic,如下:

    java代碼:?

    class Topic {
    ? ? boolean isAllowReply(){
    ? ? ? ? Calendar dueDate = Calendar.getInstance();
    ? ? ? ? dueDate.setTime(lastUpdatedTime);
    ? ? ? ? dueDate.add(Calendar.DATE, forum.timeToLive);
    ? ?
    ? ? ? ? Date now = newDate();
    ? ? ? ? return now.after(dueDate.getTime());
    ? ? }
    }



    注意這個isAllowReply方法,他和持久化完全不發生一丁點關系。在實際的開發中,我們同樣會遇到很多這種不需要持久化的業務邏輯(主要發生在日期運算、數值運算和枚舉運算方面),這種domain logic不管脫離不脫離所在的框架,它的行為都是一致的。對于這種domain logic,業務邏輯層并不需要提供封裝方法,它可以適用于任何場合。
    posted @ 2006-05-30 13:31 混沌中立 閱讀(3058) | 評論 (1)編輯 收藏

    原文地址:http://www.cnblogs.com/idior/archive/2005/07/04/186086.html

    近日?有關o/r?m的討論突然多了起來.?在這里覺得有必要澄清一些概念,?免的大家討論來討論去,?才發現最根本的理解有問題.

    1.?何謂實體?
    實體(類似于j2ee中的Entity?Bean)通常指一個承載數據的對象,?但是注意它也是可以有行為的!?只不過它的行為一般只操作自身的數據.?比如下面這個例子:


    class?Person
    {
    ??string?firstName;
    ??string?lastName;

    ??public?void?GetName()
    ??{
    ?????return??lastName+firstName;
    ??}???
    }


    GetName就是它的一個行為.

    2?何謂對象?
    對象最重要的特性在于它擁有行為.?僅僅擁有數據,你可以稱它為對象,?但是它卻失去它最重要的靈魂.?


    class?Person
    {
    ??string?firstName;
    ??string?lastName;
    ??Role?role;
    ??int?baseWage;
    ??public?void?GetSalary()
    ??{
    ?????return?baseWage*role.GetFactory();
    ??}???
    }

    這樣需要和別的對象(不是Value?Object)打交道的對象,我就不再稱其為實體.?領域模型就是指由這些具有業務邏輯的對象構成的模型.

    3.?E/R?M?or?O/R?M?!!
    仔細想想我們為什么需要o/r?m,無非是想利用oo的多態來處理復雜的業務邏輯,?而不是靠一堆的if?else.?
    而現在在很多人的手上o/r?m全變成了e/r?m.他們不考慮對象的行為,?而全關注于如何保存數據.這樣也難怪他們會產生將CRUD這些操作放入對象中的念頭.?如果你不能深刻理解oo,?那么我不推薦你使用o/r?m,?Table?Gateway,?Row?Gateway才是你想要的東西.


    作為一個O/R?M框架,很重要的一點就是實現映射的透明性(Transparent),比較顯著的特點就是在代碼中我們是看不到SQL語句的(框架自動生成了)。這里所指的O/R?M就是類似于此的框架,

    4.?POEAA中的相關概念
    ??很多次發現有人錯用其中的概念,?這里順便總結一下:
    ??1.?Table?Gateway
    ????以表為單位的實體,基本沒有行為,只有CRUD操作.
    ??2.?Row?Gateway
    ????以行為單位的實體,基本沒有行為,只有CRUD操作.
    ??3.?Active?Record
    ????以行為單位的實體,擁有一些基本的操作自身數據的行為(如上例中的GetName),同時包含有CRUD操作.
    其實Active?Record最符合某些簡單的需求,?接近于E/R?m.
    通常也有很多人把它當作O/R?m.不過需要注意的是Active?Record中是充滿了SQL語句的(不像orm的SQL透明),?所以有人想起來利用O/R?m來實現"Active?Record",?雖然在他們眼里看起來很方便,?其實根本就是返祖.
    用CodeGenerator來實現Active?Record也許是一個比較好的方法.
    ??4.?Data?Mapper
    這才是真正的O/R?m,Hibernate等等工具的目標.

    5.O/R?M需要關注的地方?(希望大家幫忙完善一下)
    ?1.?關聯,?O/R?M是如何管理類之間的關聯.當然這不僅于o/r?m有關與設計者的設計水平也有很大關系.
    ?2.?O/R?M對繼承關系的處理.
    ?3.?O/R?M對事務的支持.
    ?4.?O/R?M對查詢的支持.
    ?
    以上觀點僅屬個人意見,?不過在大家討論有關O/R?m之前,?希望先就一些基本概念達成共識,?不然討論下去會越離越遠.?


    (建議:?如果對oo以及dp沒有一定程度的了解,?最好別使用o/r?m,?dataset?加上codesmith或許是更好的選擇)
    posted @ 2006-05-30 13:20 混沌中立 閱讀(379) | 評論 (0)編輯 收藏
    僅列出標題  下一頁
    主站蜘蛛池模板: 国产一级一片免费播放| 日韩精品无码免费一区二区三区| 特级做A爰片毛片免费看无码 | 亚洲av永久中文无码精品综合| 91免费精品国自产拍在线不卡| 亚洲AV无码久久精品蜜桃| 成全在线观看免费观看大全| 亚洲av永久无码精品古装片| 无码一区二区三区AV免费| 国产99久久久久久免费看| 久久精品国产亚洲沈樵| 日本免费大黄在线观看| MM1313亚洲精品无码久久| 无码专区一va亚洲v专区在线 | 亚洲中文精品久久久久久不卡| 国产成人免费高清激情视频| 亚洲欧美日韩中文高清www777| 国产一卡二卡≡卡四卡免费乱码| 久久永久免费人妻精品| 亚洲成年人免费网站| 免费无码一区二区三区蜜桃大| 无码亚洲成a人在线观看| 国产成人精品久久亚洲| 免费91麻豆精品国产自产在线观看 | 亚洲欧美日韩综合俺去了| 亚洲综合色婷婷七月丁香| 久久永久免费人妻精品| 老司机午夜精品视频在线观看免费| 免费乱码中文字幕网站| 成人精品一区二区三区不卡免费看| 亚洲AV无码一区二区一二区| 亚洲视频在线免费观看| 成人免费a级毛片无码网站入口| 亚洲日韩中文字幕| 久久亚洲国产成人影院网站 | 亚洲人成网站在线播放2019| 亚洲国语精品自产拍在线观看| 亚洲精品国产高清嫩草影院| 免费A级毛片在线播放| 中文在线免费视频| 亚洲一区二区三区在线观看蜜桃 |