本文由 ImportNew - 文 學(xué)敏 翻譯自 javaworld。
集合類型在面向?qū)ο缶幊讨泻艹S茫@也帶來一些代碼相關(guān)的問題。比如,“怎么操作集合中不同類型的對象?”
一種做法就是遍歷集合中的每個元素,然后根據(jù)它的類型而做具體的操作。這會很復(fù)雜,尤其當(dāng)你不知道集合中元素的類型時。如果y要打印集合中的元素,可以寫一個這樣的方法:
public void messyPrintCollection(Collection collection) {
Iterator iterator = collection.iterator()
while (iterator.hasNext())
System.out.println(iterator.next().toString())
}
看起來很簡單。僅僅調(diào)用了Object.toString()方法并打印出了對象,對吧?但如果你的集合是一個包含hashtable的vector呢?那會變得更復(fù)雜。你必須檢查集合返回對象的類型:
public void messyPrintCollection(Collection collection) {
Iterator iterator = collection.iterator()
while (iterator.hasNext()) {
Object o = iterator.next();
if (o instanceof Collection)
messyPrintCollection((Collection)o);
else
System.out.println(o.toString());
}
}
好了,現(xiàn)在可以處理內(nèi)嵌的集合對象,但其他對象返回的字符串不是你想要的呢?假如你想在字符串對象加上引號,想在Float對象后加一個f,你該怎么做?代碼會變得更加復(fù)雜:
public void messyPrintCollection(Collection collection) {
Iterator iterator = collection.iterator()
while (iterator.hasNext()) {
Object o = iterator.next();
if (o instanceof Collection)
messyPrintCollection((Collection)o);
else if (o instanceof String)
System.out.println("'"+o.toString()+"'");
else if (o instanceof Float)
System.out.println(o.toString()+"f");
else
System.out.println(o.toString());
}
}
代碼很快就變雜亂了。你不想讓代碼中包含一大堆的if-else語句!怎么避免呢?訪問者模式可以幫助你。
為實現(xiàn)訪問者模式,你需要創(chuàng)建一個Visitor接口,為被訪問的集合對象創(chuàng)建一個Visitable接口。接下來需要創(chuàng)建具體的類來實現(xiàn)Visitor和Visitable接口。這兩個接口大致如下:
public interface Visitor
{
public void visitCollection(Collection collection);
public void visitString(String string);
public void visitFloat(Float float);
}
public interface Visitable
{
public void accept(Visitor visitor);
}
對于一個具體的String類,可以這么實現(xiàn):
public class VisitableString implements Visitable
{
private String value;
public VisitableString(String string) {
value = string;
}
public void accept(Visitor visitor) {
visitor.visitString(this);
}
}
在accept方法中,根據(jù)不同的類型,調(diào)用visitor中對應(yīng)的方法:
visitor.visitString(this)
具體Visitor的實現(xiàn)方式如下:
public class PrintVisitor implements Visitor
{
public void visitCollection(Collection collection) {
Iterator iterator = collection.iterator();
while (iterator.hasNext()) {
Object o = iterator.next();
if (o instanceof Visitable)
((Visitable)o).accept(this);
}
public void visitString(String string) {
System.out.println("'"+string+"'");
}
public void visitFloat(Float float) {
System.out.println(float.toString()+"f");
}
}
到時候,只要實現(xiàn)了VisitableFloat類和VisitableCollection類并調(diào)用合適的visitor方法,你就可以去掉包含一堆if-else結(jié)構(gòu)的messyPrintCollection方法,采用一種十分清爽的方式實現(xiàn)了同樣的功能。visitCollection()方法調(diào)用了Visitable.accept(this),而accept()方法又反過來調(diào)用了visitor中正確的方法。這就是雙分派:Visitor調(diào)用了一個Visitable類中的方法,這個方法又反過來調(diào)用了Visitor類中的方法。
盡管實現(xiàn)visitor后,if-else語句不見了,但還是引入了很多附加的代碼。你不得不將原始的對象——String和Float,打包到一個實現(xiàn)Visitable接口的類中。雖然很煩人,但這一般來說不是個問題。因為你可以限制被訪問集合只能包含Visitable對象。
然而,這還有很多附加的工作要做。更壞的是,當(dāng)你想增加一個新的Visitable類型時怎么辦,比如VisitableInteger?這是訪問者模式的一個主要缺點。如果你想增加一個新的Visitable類型,你不得不改變Visitor接口以及每個實現(xiàn)Visitor接口方法的類。你可以不把Visitor設(shè)計為接口,取而代之,可以把Visitor設(shè)計為一個帶有空操作的抽象基類。這與Java GUI中的Adapter類很相似。這么做的問題是你會用盡單次繼承,而常見的情形是你還想用繼承實現(xiàn)其他功能,比如繼承StringWriter類。這同樣只能成功訪問實現(xiàn)Visitable接口的對象。
幸運(yùn)的是,Java可以讓你的訪問者模式更靈活,你可以按你的意愿增加Visitable對象。怎么實現(xiàn)呢?答案是使用反射。使用反射的ReflectiveVisitor接口只需要一個方法:
public interface ReflectiveVisitor {
public void visit(Object o);
}
好了,上面很簡單。Visitable接口先不動,待會我會說。現(xiàn)在,我使用反射實現(xiàn)PrintVisitor類。
public class PrintVisitor implements ReflectiveVisitor {
public void visitCollection(Collection collection)
{ ... same as above ... }
public void visitString(String string)
{ ... same as above ... }
public void visitFloat(Float float)
{ ... same as above ... }
public void default(Object o)
{
System.out.println(o.toString());
}
public void visit(Object o) {
// Class.getName() returns package information as well.
// This strips off the package information giving us
// just the class name
String methodName = o.getClass().getName();
methodName = "visit"+
methodName.substring(methodName.lastIndexOf('.')+1);
// Now we try to invoke the method visit<methodName>
try {
// Get the method visitFoo(Foo foo)
Method m = getClass().getMethod(methodName,
new Class[] { o.getClass() });
// Try to invoke visitFoo(Foo foo)
m.invoke(this, new Object[] { o });
} catch (NoSuchMethodException e) {
// No method, so do the default implementation
default(o);
}
}
}
現(xiàn)在你無需使用Visitable包裝類(包裝了原始類型String、Float)。你可以直接訪問visit(),它會調(diào)用正確的方法。visit()的一個優(yōu)點是它會分派它認(rèn)為合適的方法。這不一定使用反射,可以使用完全不同的一種機(jī)制。
在新的PrintVisitor類中,有對應(yīng)于Collections、String和Float的操作方法;對于不能處理的類型,可以通過catch語句捕捉。對于不能處理的類型,可以通過擴(kuò)展visit()方法來嘗試處理它們的所有超類。首先,增加一個新的方法getMethod(Class c),返回值是一個可被觸發(fā)的方法。它會搜索Class c的所有父類和接口,以找到一個匹配方法。
protected Method getMethod(Class c) {
Class newc = c;
Method m = null;
// Try the superclasses
while (m == null && newc != Object.class) {
String method = newc.getName();
method = "visit" + method.substring(method.lastIndexOf('.') + 1);
try {
m = getClass().getMethod(method, new Class[] {newc});
} catch (NoSuchMethodException e) {
newc = newc.getSuperclass();
}
}
// Try the interfaces. If necessary, you
// can sort them first to define 'visitable' interface wins
// in case an object implements more than one.
if (newc == Object.class) {
Class[] interfaces = c.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
String method = interfaces[i].getName();
method = "visit" + method.substring(method.lastIndexOf('.') + 1);
try {
m = getClass().getMethod(method, new Class[] {interfaces[i]});
} catch (NoSuchMethodException e) {}
}
}
if (m == null) {
try {
m = thisclass.getMethod("visitObject", new Class[] {Object.class});
} catch (Exception e) {
// Can't happen
}
}
return m;
}
這看上去很復(fù)雜,實際上并不。大致來說,首先根據(jù)傳入的class名稱搜索可用方法;如果沒找到,就嘗試從父類搜索;如果還沒找到,就從接口中嘗試。后,(仍沒找到)可以使用visitObject()作為默認(rèn)方法。
由于大家對傳統(tǒng)的訪問者模式比較熟悉,這里沿用了之前方法命名的慣例。但是,有些人可能注意到,把所有的方法都命名為“visit”并通過參數(shù)類型不同來區(qū)分,這樣更高效。然而,如果你這么做,你必須把visit(Object o)方法的名稱改為其他,比如dispatch(Object o)。否則,(當(dāng)沒有對應(yīng)處理方法時),你無法退回到默認(rèn)的處理方法,并且當(dāng)你調(diào)用visit(Object o)方法時,為了確保正確的方法調(diào)用,你必須將參數(shù)強(qiáng)制轉(zhuǎn)化為Object。
為了利用getMethod()方法,現(xiàn)在需要修改一下visit()方法。
public void visit(Object object) {
try {
Method method = getMethod(getClass(), object.getClass());
method.invoke(this, new Object[] {object});
} catch (Exception e) { }
}
現(xiàn)在,visitor類更加強(qiáng)大了——可以傳入任意的對象并且有對應(yīng)的處理方法。另外,有一個默認(rèn)處理方法,visitObject(Object o),的好處就是就可以捕捉到任何沒有明確說明的類型。再稍微修改下,你甚至可以添加一個visitNull()方法。
我仍保留Visitable接口是有原因的。傳統(tǒng)訪問者模式的另一個好處是它可以通過Visitable對象控制對象結(jié)構(gòu)的遍歷順序。舉例來說,假如有一個實現(xiàn)了Visitable接口的類TreeNode,它在accept()方法中遍歷自己的左右節(jié)點。
public void accept(Visitor visitor) {
visitor.visitTreeNode(this);
visitor.visitTreeNode(leftsubtree);
visitor.visitTreeNode(rightsubtree);
}
這樣,只要修改下Visitor類,就可以通過Visitable類控制遍歷:
public void visit(Object object) throws Exception
{
Method method = getMethod(getClass(), object.getClass());
method.invoke(this, new Object[] {object});
if (object instanceof Visitable)
{
callAccept((Visitable) object);
}
}
public void callAccept(Visitable visitable) {
visitable.accept(this);
}
如果你實現(xiàn)了Visitable對象的結(jié)構(gòu),你可以保持callAccept()不變,就可以使用Visitable控制的對象遍歷。如果你想在visitor中遍歷對象結(jié)構(gòu),你只需重寫allAccept()方法,讓它什么都不做。
當(dāng)使用幾個不同的visitor去操作同一個對象集合時,訪問者模式的力量就會展現(xiàn)出來。比如,當(dāng)前有一個解釋器、中序遍歷器、后續(xù)遍歷器、XML編寫器以及SQL編寫器,它們可以處理同一個對象集合。我可以輕松地為這個集合再寫一個先序遍歷器或者一個SOAP編寫器。另外,它們可以很好地兼容它們不識別的類型,或者我愿意的話可以讓它們拋出異常。
總結(jié)
使用Java反射,可以使訪問者模式提供一種更加強(qiáng)大的方式操作對象結(jié)構(gòu),可以按照需求靈活地增加新的Visitable類型。我希望在你的編程之旅中可以使用訪問者模式。
原文鏈接: javaworld 翻譯: ImportNew.com - 文 學(xué)敏
譯文鏈接: http://www.importnew.com/12536.html
本站文章版權(quán)歸原作者及原出處所有 。內(nèi)容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負(fù)責(zé),本站只提供參考并不構(gòu)成任何投資及應(yīng)用建議。本站是一個個人學(xué)習(xí)交流的平臺,網(wǎng)站上部分文章為轉(zhuǎn)載,并不用于任何商業(yè)目的,我們已經(jīng)盡可能的對作者和來源進(jìn)行了通告,但是能力有限或疏忽,造成漏登,請及時聯(lián)系我們,我們將根據(jù)著作權(quán)人的要求,立即更正或者刪除有關(guān)內(nèi)容。本站擁有對此聲明的最終解釋權(quán)。