
2006年7月13日
在一個(gè)項(xiàng)目里面有這么一個(gè)技術(shù)需求:
1.集合中元素個(gè)數(shù),10M
2.根據(jù)上限和下限從一個(gè)Set中過濾出滿足要求的元素集合.
實(shí)際這個(gè)是個(gè)很典型的技術(shù)要求, 之前的項(xiàng)目也遇見過,但是因?yàn)楫?dāng)時(shí)的類庫不多, 都是直接手寫實(shí)現(xiàn)的. 方式基本等同于第一個(gè)方式.
在這個(gè)過程中, 我寫了四個(gè)方式, 基本記錄到下面.
第一個(gè)方式:對(duì)Set進(jìn)行迭代器遍歷, 判斷每個(gè)元素是否都在上限和下限范圍中.如果滿足則添加到結(jié)果集合中, 最后返回結(jié)果集合.
測試效果:集合大小100K, 運(yùn)算時(shí)間 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 }
第二個(gè)方式: 借助TreeSet, 原始集合進(jìn)行排序, 然后直接subset.
測試效果: 集合大小10M, 運(yùn)算時(shí)間: 12000ms+(獲得TreeSet) , 200ms(獲得結(jié)果)
過濾部分的邏輯如下(非常繁瑣):
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, 直接對(duì)于原始Set進(jìn)行filter.
測試效果:集合大小10M,過濾結(jié)果1M, 運(yùn)算時(shí)間: 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), 直接對(duì)于原始Set進(jìn)行Filter
測試效果:集合大小10M,過濾結(jié)果1M, 運(yùn)算時(shí)間: 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 }
四種方式對(duì)比如下:
第一種方式: 僅依賴于JAVA原生類庫 遍歷時(shí)間最慢, 代碼量很小
第二種方式: 僅依賴于JAVA原生類庫 遍歷時(shí)間比較慢(主要慢在生成有序Set), 代碼量最多
第三種方式: 依賴于Apache Commons Collections, 遍歷時(shí)間比較快, 代碼量很少
第四種方式: 依賴于Guava, 遍歷時(shí)間最快, 代碼量很少
基于目前個(gè)人的技術(shù)水平和視野, 第四種方式可能是最佳選擇.
記錄一下, 以后可能還會(huì)有更好的方案.
在幾年之前,在大學(xué)里面的時(shí)候,認(rèn)為系統(tǒng)的架構(gòu)設(shè)計(jì),就像建筑設(shè)計(jì)一樣,會(huì)把骨架搭成,然后有具體人員進(jìn)行詳細(xì)的開發(fā).
在后來,工作中,慢慢有了一些變化,因?yàn)樵鹊南敕ú惶泻蠈?shí)際,系統(tǒng)就是在變化之中的,如果固定了骨架,那就很難的敏捷面對(duì)變化.
所以,系統(tǒng)的架構(gòu)設(shè)計(jì),應(yīng)該是面向接口的設(shè)計(jì),確定各個(gè)部分之間的數(shù)據(jù)接口和方法接口.這樣,即使有了變化,只要遵循接口的定義,就還是可以面對(duì)變化.
最近,又有了想法的變化.架構(gòu)的設(shè)計(jì),應(yīng)該就是規(guī)則和規(guī)約的設(shè)計(jì).設(shè)計(jì)出一系列,統(tǒng)一的,和諧的規(guī)則,在這些規(guī)則之前圈住的部分,實(shí)際就是系統(tǒng)的全貌.
接口的設(shè)計(jì),實(shí)際只是規(guī)則和規(guī)約設(shè)計(jì)的一個(gè)部分.
架構(gòu)的設(shè)計(jì),不應(yīng)該只是程序方面的事情,同時(shí)也包含了心理學(xué)方面和社會(huì)學(xué)方面的一些規(guī)則.例如:團(tuán)隊(duì)在面對(duì)變化時(shí)候,需要采用的規(guī)則和流程.
只有包含了這些非程序上的規(guī)則之后,才能保證架構(gòu)風(fēng)格的統(tǒng)一協(xié)調(diào).
以上,是我對(duì)系統(tǒng)設(shè)計(jì)的想法的轉(zhuǎn)變過程.記錄于此,以供回溯.
面對(duì)著滿屏幕的程序
是三年前,項(xiàng)目剛剛啟動(dòng)的時(shí)候,同事寫的代碼.
三年過去了,項(xiàng)目由第一期變成了第七期.
這段代碼還是在這里,有個(gè)屬性是list,其中每個(gè)cell都是一個(gè)長度18的String數(shù)組.
數(shù)組里面放置了所需要導(dǎo)出到頁面table的內(nèi)容.
現(xiàn)在要開始修改了,需要向頁面的table中增加4列.
繁瑣的讓人要命的工作,需要跟蹤這個(gè)循環(huán),判斷每個(gè)pattern下面,這個(gè)長度18的數(shù)組里面放了哪些內(nèi)容.
好吧,對(duì)象化維護(hù)從數(shù)組開始,把數(shù)組對(duì)折,因?yàn)檫@個(gè)數(shù)組時(shí)一個(gè)比較數(shù)組,前面9個(gè)元素是之前的情況,后面9個(gè)事之后的情況.
用一個(gè)bean,放入兩次就可以了.但是bean中,需要一個(gè)標(biāo)志,標(biāo)識(shí)是之前的情況還是之后的情況.
同時(shí)需要一個(gè)transform方法,把之前從幾個(gè)來源過來的情況,變成bean的屬性.
接下來需要一個(gè)values方法,把bean里面的屬性直接按順序轉(zhuǎn)化成數(shù)組.
本期新增的4個(gè)屬性,直接放入bean中就可以了.
這樣原來很復(fù)雜的數(shù)組,就可以簡單的用對(duì)象來解決.外部的接口完全沒有變化.
維護(hù)程序,從把數(shù)組(特別是異型數(shù)組)對(duì)象化開始.
這個(gè)小的project是前一個(gè)階段,待業(yè)在家的時(shí)候,迷戀sudoku的時(shí)候,自己寫來玩的。
正好當(dāng)時(shí)在看Uncle Bob的《Agile Software Development: Principles, Patterns, and Practices》 (敏捷軟件開發(fā):原則、模式與實(shí)踐),所以就按照自己對(duì)書中的一些概念和方法的理解,結(jié)合自己之前的開發(fā)經(jīng)驗(yàn)寫出來一段小的代碼。
代碼行數(shù): < 900
類的個(gè)數(shù): 18
抽象類的個(gè)數(shù):2
工廠類的個(gè)數(shù):1
包的個(gè)數(shù):5
一些關(guān)于類和包作用的說明:
1.Cell:表示一個(gè)Cell,是一個(gè)游戲中的一個(gè)單元格。
? Cell主要由3個(gè)部分組成,Point,Value,Status.
2.Point:表示一個(gè)坐標(biāo),主要格式為:(2,3).
? !!!注意:由于個(gè)人比較懶,所以開始的錯(cuò)誤被貫徹了下來。
? 這個(gè)錯(cuò)誤就是(2,3)表示的是由最左上的位置為坐標(biāo)原點(diǎn),第二行和第三列所確定的那個(gè)單元格。也就是縱坐標(biāo)在前,橫坐標(biāo)在后了。
3.Value:表示一個(gè)值
4.Status:表示Cell的狀態(tài),只有兩個(gè)狀態(tài),一個(gè)是NotSure,另一個(gè)是Sure.
5.AbstractCells:表示一些cell的集合,主要有三個(gè)子類
???? BlockCells:表示一個(gè)由多個(gè)Cell組成的塊,例如一個(gè)2*2由4個(gè)Cell組成的塊,或者一個(gè)2*3由6個(gè)Cell組成的塊
???? HorizonCells:表示一個(gè)橫行,即:從(0,0)到(0,n)坐標(biāo)確定的所有Cell的集合。
???? VerticalCells:表示一個(gè)縱行,即:從(0,0)到(n,0)坐標(biāo)確定的所有Cell的集合。
6.AbstractPolicy:就是游戲的策略。
?? 這個(gè)主要表示的是:4*4的游戲,還是9*9的游戲。
?? 可以在以后對(duì)此類進(jìn)行繼承和擴(kuò)展,例如16*16的游戲我就沒有實(shí)現(xiàn)。
?? 主要擴(kuò)展3個(gè)方法:
????????????????? 1)getValueRange,返回當(dāng)前policy的value的個(gè)數(shù)。4*4的游戲的getValueRange返回的就應(yīng)該是4。
??? ??? ? 2)getStep:表示當(dāng)前policy中相鄰的兩個(gè)BlockCells的坐標(biāo)差。
??? ??? ? 3)getIncrease:說不明白了:)(只可意會(huì)不可言傳。)
7.Game:進(jìn)行Policy的場所(我一直想拋棄這個(gè)類)
8.TestGame:游戲運(yùn)行的地方,包括從PolicyFactory取得指定的Policy,設(shè)置輸入輸出文件的路徑。
9.PolicyFactory:取得Policy的工廠。
??? getPolicy(int x) :這個(gè)方法獲得的是正方形的sudoku的策略。例如:4*4的,9*9,16*16。
??? getPolicy(int x, int y):這個(gè)方法獲得的是長方形的Sudoku的策略。例如:9*12的。
雖然是盡量避免bad code smell,但是由于能力有限,還是出現(xiàn)了一些不好的地方。
例如:之間的關(guān)聯(lián)關(guān)系還是很多,而且很強(qiáng);抽象的方法和抽象類的個(gè)數(shù)偏少等等。
里面實(shí)現(xiàn)了三個(gè)解決sudoku的方法:
1.在一個(gè)Cell中出現(xiàn)的Value,不會(huì)在和這個(gè)Cell處在同一個(gè)AbstractCells中的所有Cell中出現(xiàn);
2.如果一個(gè)Cell中,所有可能出現(xiàn)的Value的個(gè)數(shù)為1,那么Cell的Value必然是這個(gè)最后的Value;
2.如果一個(gè)Value,如果在當(dāng)前AbstractCells的所有其他的Cell中都不可能出現(xiàn),那么它必然是最后一個(gè)Cell的Value。
附件1:src code
http://www.aygfsteel.com/Files/GandofYan/sudoku.rar
附件2:輸入輸出文件的example
http://www.aygfsteel.com/Files/GandofYan/temp.rar