2.Commons Collections中的算子
算子成為Commons Collections 3.1中的有趣的部分有兩個原因:它們沒有得到應(yīng)得的重視并且它們有改變你編程的方式的潛力。算子只是一個奇特的名字,它代表了一個包裝了函數(shù)的對象—一個“函數(shù)對象”。當(dāng)然,它們不是一回事。如果你曾經(jīng)使用過C和C++的方法指針,你就會理解算子的威力。
一個算子是一個對象—一個Predicate,一個Closure, 一個Transformer。
Predicates求對象的值并返回一個boolean,Transformer求對象的值并返回新對象,Closure接受對象并執(zhí)行代碼。算子可以被組合成組合算子來模仿循環(huán),邏輯表達(dá)式,和控制結(jié)構(gòu),并且算子也可以被用來過濾和操作集合中的元素。在這么短的篇幅中解釋清楚算子是不可能的,所以跳過介紹,我將會通過使用和不使用算子來解決同一問題(解釋算子)。在這個例子中,從一個ArrayList中而來的Student對象會被排序到兩個List中,如果他們符合某種標(biāo)準(zhǔn)的話。
成績?yōu)锳的學(xué)生會被加到honorRollStudents(光榮榜)中,得D和F的學(xué)生被加到problemStudents (問題學(xué)生)list中。學(xué)生分開以后,系統(tǒng)將會遍歷每個list,給加入到光榮榜中學(xué)生一個獎勵,并安排與問題學(xué)生的家長談話的時間表。下面的代碼不使用算子實(shí)現(xiàn)了這個過程:
上述例子是非常過程化的;要想知道Student對象發(fā)生了什么事必須遍歷每一行代碼。例子的第一部分是基于成績和考勤對Student對象進(jìn)行邏輯判斷。
第二部分對Student對象進(jìn)行操作并存儲到數(shù)據(jù)庫中。像上述這個有著50行代碼程序也是大多程序所開始的—可管理的過程化的復(fù)雜性。但是當(dāng)需求變化時,問題出現(xiàn)了。一旦判斷邏輯改變,你就需要在第一部分中增加更多的邏輯表達(dá)式。
舉例來說,如果一個有著成績B和良好出勤記錄,但有五次以上的留堂記錄的學(xué)生被判定為問題學(xué)生,那么你的邏輯表達(dá)式將會如何處理?或者對于第二部分中,只有在上一年度不是問題學(xué)生的學(xué)生才能進(jìn)入光榮榜的話,如何處理?當(dāng)例外和需求開始改變進(jìn)而影響到過程代碼時,可管理的復(fù)雜性就會變成不可維護(hù)的面條式的代碼。
從上面的例子中回來,考慮一下那段代碼到底在做什么。它在一個List遍歷每一個對象,檢查標(biāo)準(zhǔn),如果適用該標(biāo)準(zhǔn),對此對象進(jìn)行某些操作。上述例子可以進(jìn)行改進(jìn)的關(guān)鍵一處在于從代碼中將標(biāo)準(zhǔn)與動作解藕開來。下面的兩處代碼引用以一種非常不同的方法解決了上述的問題。首先,榮譽(yù)榜和問題學(xué)生的標(biāo)準(zhǔn)被兩個Predicate對象模型化了,并且加之于榮譽(yù)學(xué)生和問題學(xué)生上的動作也被兩個Closure對象模型化了。這四個對象如下定義:
這四個匿名的Predicate和Closure是從作為一個整體互相分離的。flagForAttention(標(biāo)記為注意)并不知道什么是確定一個問題學(xué)生的標(biāo)準(zhǔn) 。現(xiàn)在需要的是將正確的Predicate和正確的Closure結(jié)合起來的方法,這將在下面的例子中展示:
在上面的代碼中,predicateMap將Predicate與Closure進(jìn)行了配對;如果一個學(xué)生滿足作為鍵值的Predicate的條件,那么它將把它的值傳到作為Map的值的Closure中。通過提供一個NOPClosure值和null鍵對,我們將把不符合任何Predicate條件的Student對象傳給由ClosureUtils調(diào)用創(chuàng)建的“不做任何事”或者“無操作”的NOPClosure。
一個SwitchClosure, processStudents,從predicateMap中創(chuàng)建。并且通過使用CollectionUtils.forAllDo()方法,將processStudents Closure應(yīng)用到allStudents中的每一個Student對象上。這是非常不一樣的處理方法;記住,你并沒有遍歷任何隊(duì)列。而是通過設(shè)置規(guī)則和因果關(guān)系,以及CollectionUtils和SwitchClosur來完成了這些操作。
當(dāng)你將使用Predicate的標(biāo)準(zhǔn)與使用Closure的動作將分離開來時,你的代碼的過程式處理就少了,而且更容易測試了。isHonorRoll Predicate能夠與addToHonorRoll Closure分離開來進(jìn)行獨(dú)立的單元測試,它們也可以合起來通過使用Student類的模仿對象進(jìn)行測試。第二個例子也會演示CollectionUtils.forAllDo(),它將一個Closure應(yīng)用到了一個Collection的每一個元素中。
你也許注意到了使用算子并沒用減少代碼行數(shù),實(shí)際上,使用算子還增加了代碼量。但是,通過算子,你得到了將到了標(biāo)準(zhǔn)與動作的模塊性與封裝性的好處。如果你的代碼題已經(jīng)接近于幾百行,那么請考慮一下更少過程化處理,更多面向?qū)ο蟮慕鉀Q方案—通過使用算子。
Jakarta Commons Cookbook中的第四章“算子”介紹了Commons Collections中可用的算子,在第五章,“集合”中,向你展示了如何使用算子來操作Java 集合類API。
所有的算子-- Closure, Predicate, 和 Transformer—能夠被合并為合并算子來處理任何種類的邏輯問題。switch, while和for結(jié)構(gòu)能夠被SwitchClosure, WhileClosure, 和 ForClosure模型化。
復(fù)合的邏輯表達(dá)式可以被多個Predicate構(gòu)建,通過使用OrPredicate, AndPredicate, AllPredicate, 和 NonePredicate將它們相互聯(lián)接。Commons BeanUtils也包含了算子的實(shí)現(xiàn)被用來將算子應(yīng)用到bean的屬性中-- BeanPredicate, BeanComparator, 和 BeanPropertyValueChangeClosure。算子是考慮底層的應(yīng)用架構(gòu)的不一樣的方法,它們可以很好地改造你編碼實(shí)現(xiàn)的方法。
算子成為Commons Collections 3.1中的有趣的部分有兩個原因:它們沒有得到應(yīng)得的重視并且它們有改變你編程的方式的潛力。算子只是一個奇特的名字,它代表了一個包裝了函數(shù)的對象—一個“函數(shù)對象”。當(dāng)然,它們不是一回事。如果你曾經(jīng)使用過C和C++的方法指針,你就會理解算子的威力。
一個算子是一個對象—一個Predicate,一個Closure, 一個Transformer。
Predicates求對象的值并返回一個boolean,Transformer求對象的值并返回新對象,Closure接受對象并執(zhí)行代碼。算子可以被組合成組合算子來模仿循環(huán),邏輯表達(dá)式,和控制結(jié)構(gòu),并且算子也可以被用來過濾和操作集合中的元素。在這么短的篇幅中解釋清楚算子是不可能的,所以跳過介紹,我將會通過使用和不使用算子來解決同一問題(解釋算子)。在這個例子中,從一個ArrayList中而來的Student對象會被排序到兩個List中,如果他們符合某種標(biāo)準(zhǔn)的話。
成績?yōu)锳的學(xué)生會被加到honorRollStudents(光榮榜)中,得D和F的學(xué)生被加到problemStudents (問題學(xué)生)list中。學(xué)生分開以后,系統(tǒng)將會遍歷每個list,給加入到光榮榜中學(xué)生一個獎勵,并安排與問題學(xué)生的家長談話的時間表。下面的代碼不使用算子實(shí)現(xiàn)了這個過程:
List allStudents = getAllStudents();
// 創(chuàng)建兩個ArrayList來存放榮譽(yù)學(xué)生和問題學(xué)生
List honorRollStudents = new ArrayList();
List problemStudents = new ArrayList();
// 遍歷所有學(xué)生,將榮譽(yù)學(xué)生放入一個List,問題學(xué)生放入另一個
Iterator allStudentsIter = allStudents.iterator();
while( allStudentsIter.hasNext() ) {
Student s = (Student) allStudentsIter.next();
if( s.getGrade().equals( "A" ) ) {
honorRollStudents.add( s );
} else if( s.getGrade().equals( "B" ) &&
s.getAttendance() == PERFECT) {
honorRollStudents.add( s );
} else if( s.getGrade().equals( "D" ) ||
s.getGrade().equals( "F" ) ) {
problemStudents.add( s );
} else if( s.getStatus() == SUSPENDED ) {
problemStudents.add( s );
}
}
// 對于的有榮譽(yù)學(xué)生,增加一個獎勵并存儲到數(shù)據(jù)庫中
Iterator honorRollIter =
honorRollStudents.iterator();
while( honorRollIter.hasNext() ) {
Student s = (Student) honorRollIter.next();
// 給學(xué)生記錄增加一個獎勵
s.addAward( "honor roll", 2005 );
Database.saveStudent( s );
}
// 對所有問題學(xué)生,增加一個注釋并存儲到數(shù)據(jù)庫中
Iterator problemIter = problemStudents.iterator();
while( problemIter.hasNext() ) {
Student s = (Student) problemIter.next();
// 將學(xué)生標(biāo)記為需特殊注意
s.addNote( "talk to student", 2005 );
s.addNote( "meeting with parents", 2005 );
Database.saveStudent( s );
}
上述例子是非常過程化的;要想知道Student對象發(fā)生了什么事必須遍歷每一行代碼。例子的第一部分是基于成績和考勤對Student對象進(jìn)行邏輯判斷。
第二部分對Student對象進(jìn)行操作并存儲到數(shù)據(jù)庫中。像上述這個有著50行代碼程序也是大多程序所開始的—可管理的過程化的復(fù)雜性。但是當(dāng)需求變化時,問題出現(xiàn)了。一旦判斷邏輯改變,你就需要在第一部分中增加更多的邏輯表達(dá)式。
舉例來說,如果一個有著成績B和良好出勤記錄,但有五次以上的留堂記錄的學(xué)生被判定為問題學(xué)生,那么你的邏輯表達(dá)式將會如何處理?或者對于第二部分中,只有在上一年度不是問題學(xué)生的學(xué)生才能進(jìn)入光榮榜的話,如何處理?當(dāng)例外和需求開始改變進(jìn)而影響到過程代碼時,可管理的復(fù)雜性就會變成不可維護(hù)的面條式的代碼。
從上面的例子中回來,考慮一下那段代碼到底在做什么。它在一個List遍歷每一個對象,檢查標(biāo)準(zhǔn),如果適用該標(biāo)準(zhǔn),對此對象進(jìn)行某些操作。上述例子可以進(jìn)行改進(jìn)的關(guān)鍵一處在于從代碼中將標(biāo)準(zhǔn)與動作解藕開來。下面的兩處代碼引用以一種非常不同的方法解決了上述的問題。首先,榮譽(yù)榜和問題學(xué)生的標(biāo)準(zhǔn)被兩個Predicate對象模型化了,并且加之于榮譽(yù)學(xué)生和問題學(xué)生上的動作也被兩個Closure對象模型化了。這四個對象如下定義:
import org.apache.commons.collections.Closure;
import org.apache.commons.collections.Predicate;
// 匿名的Predicate決定一個學(xué)生是否加入榮譽(yù)榜
Predicate isHonorRoll = new Predicate() {
public boolean evaluate(Object object) {
Student s = (Student) object;
return( ( s.getGrade().equals( "A" ) ) ||
( s.getGrade().equals( "B" ) &&
s.getAttendance() == PERFECT ) );
}
};
//匿名的Predicate決定一個學(xué)生是否是問題學(xué)生
Predicate isProblem = new Predicate() {
public boolean evaluate(Object object) {
Student s = (Student) object;
return ( ( s.getGrade().equals( "D" ) ||
s.getGrade().equals( "F" ) ) ||
s.getStatus() == SUSPENDED );
}
};
//匿名的Closure將一個學(xué)生加入榮譽(yù)榜
Closure addToHonorRoll = new Closure() {
public void execute(Object object) {
Student s = (Student) object;
// 對學(xué)生增加一個榮譽(yù)記錄
s.addAward( "honor roll", 2005 );
Database.saveStudent( s );
}
};
// 匿名的Closure將學(xué)生標(biāo)記為需特殊注意
Closure flagForAttention = new Closure() {
public void execute(Object object) {
Student s = (Student) object;
// 標(biāo)記學(xué)生為需特殊注意
s.addNote( "talk to student", 2005 );
s.addNote( "meeting with parents", 2005 );
Database.saveStudent( s );
}
};
這四個匿名的Predicate和Closure是從作為一個整體互相分離的。flagForAttention(標(biāo)記為注意)并不知道什么是確定一個問題學(xué)生的標(biāo)準(zhǔn) 。現(xiàn)在需要的是將正確的Predicate和正確的Closure結(jié)合起來的方法,這將在下面的例子中展示:
import org.apache.commons.collections.ClosureUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.functors.NOPClosure;
Map predicateMap = new HashMap();
predicateMap.put( isHonorRoll, addToHonorRoll );
predicateMap.put( isProblem, flagForAttention );
predicateMap.put( null, ClosureUtils.nopClosure() );
Closure processStudents =
ClosureUtils.switchClosure( predicateMap );
CollectionUtils.forAllDo( allStudents, processStudents );
在上面的代碼中,predicateMap將Predicate與Closure進(jìn)行了配對;如果一個學(xué)生滿足作為鍵值的Predicate的條件,那么它將把它的值傳到作為Map的值的Closure中。通過提供一個NOPClosure值和null鍵對,我們將把不符合任何Predicate條件的Student對象傳給由ClosureUtils調(diào)用創(chuàng)建的“不做任何事”或者“無操作”的NOPClosure。
一個SwitchClosure, processStudents,從predicateMap中創(chuàng)建。并且通過使用CollectionUtils.forAllDo()方法,將processStudents Closure應(yīng)用到allStudents中的每一個Student對象上。這是非常不一樣的處理方法;記住,你并沒有遍歷任何隊(duì)列。而是通過設(shè)置規(guī)則和因果關(guān)系,以及CollectionUtils和SwitchClosur來完成了這些操作。
當(dāng)你將使用Predicate的標(biāo)準(zhǔn)與使用Closure的動作將分離開來時,你的代碼的過程式處理就少了,而且更容易測試了。isHonorRoll Predicate能夠與addToHonorRoll Closure分離開來進(jìn)行獨(dú)立的單元測試,它們也可以合起來通過使用Student類的模仿對象進(jìn)行測試。第二個例子也會演示CollectionUtils.forAllDo(),它將一個Closure應(yīng)用到了一個Collection的每一個元素中。
你也許注意到了使用算子并沒用減少代碼行數(shù),實(shí)際上,使用算子還增加了代碼量。但是,通過算子,你得到了將到了標(biāo)準(zhǔn)與動作的模塊性與封裝性的好處。如果你的代碼題已經(jīng)接近于幾百行,那么請考慮一下更少過程化處理,更多面向?qū)ο蟮慕鉀Q方案—通過使用算子。
Jakarta Commons Cookbook中的第四章“算子”介紹了Commons Collections中可用的算子,在第五章,“集合”中,向你展示了如何使用算子來操作Java 集合類API。
所有的算子-- Closure, Predicate, 和 Transformer—能夠被合并為合并算子來處理任何種類的邏輯問題。switch, while和for結(jié)構(gòu)能夠被SwitchClosure, WhileClosure, 和 ForClosure模型化。
復(fù)合的邏輯表達(dá)式可以被多個Predicate構(gòu)建,通過使用OrPredicate, AndPredicate, AllPredicate, 和 NonePredicate將它們相互聯(lián)接。Commons BeanUtils也包含了算子的實(shí)現(xiàn)被用來將算子應(yīng)用到bean的屬性中-- BeanPredicate, BeanComparator, 和 BeanPropertyValueChangeClosure。算子是考慮底層的應(yīng)用架構(gòu)的不一樣的方法,它們可以很好地改造你編碼實(shí)現(xiàn)的方法。