Java Tip: 用Reflection實現Visitor模式 (轉)
Tip: 用Reflection實現Visitor
概述
Visitor模式的常用之處在於,它將集合的結構和對集合所的操作分離開來。例如,它可以將一箇中的分析邏輯和程式碼生成邏輯分離開來。有了這樣的分離,想使用不同的程式碼生成器就會很容易。更大的好處還有,其它一些公用,如lint,可以在使用分析邏輯的同時免受程式碼生成邏輯之累。不幸的是,向集合中增加新的物件往往需要修改已經寫好的Visitor類。本文提出了一種在Java中實現Visitor模式的更靈活的方法:使用Reflection(反射)。
-------------------------------------------------------------
集合(Collection)普遍應用於物件導向中,但它也經常引發一些和程式碼有關的疑問。例如,"如果一個集合存在不同的物件,該如何對它執行操作?"
一種方法是,對集合中的每個元素進行迭代,然後基於所在的類,對每個元素分別執行對應的操作。這會很難辦,特別是,如果你不知道集合中有什麼型別的物件。例如,假設想列印集合中的元素,你可以寫出如下的一個方法(method):
public void messyPrintCollection(Collection collection) {
Iterator iterator = collection.iterator()
while (iterator.hasNext())
System.out.println(iterator.next().toString())
}
這看起來夠簡單的了。它只不過了.toString()方法,然後列印出物件,對嗎?但如果有一組雜湊表怎麼辦?事情就會開始變得複雜起來。你必須檢查從集合中返回的物件的型別:
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());
}
}
不錯,現在已經解決了巢狀集合的問題,但它需要物件返回String,如果有其它不返回String的物件存在怎麼辦?如果想在String物件前後新增引號以及在Float後新增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語句!那怎麼避免呢?Visitor模式可以幫你。
要實現Visitor模式,得為訪問者建立一個Visitor介面,還要為被訪問的集合建立一個Visitable介面。然後,讓具體類實現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,可能是這樣:
public class VisitableString implements Visitable
{
private String value;
public VisitableString(String string) {
value = string;
}
public void accept(Visitor visitor) {
visitor.visitString(this);
}
}
在accept方法中,對this型別呼叫正確的visitor方法:
visitor.visitString(this)
這樣,就可以如下實現具體的Visitor:
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");
}
}
實現VisitableFloat和VisitableCollection類的時候,它們也是各自呼叫合適的Visitor方法,所得到的效果和前面那個用了if-else的messyPrintCollection方法一樣,但這裡的手法更乾淨。在visitCollection()中,呼叫的是Visitable.accept(this),然後這個呼叫又返回去呼叫一個合適的Visitor方法。這被稱做 "雙分派";即,Visitor先呼叫了Visitable類中的方法,這個方法又回撥到Visitor類中。
雖然透過實現visitor消除了if-else語句,卻也增加了很多額外的程式碼。最初的String和Float物件都要用實現了Visitable介面的物件進行包裝。這有點討厭,但一般說來不是問題,因為你可以讓經常被訪問的集合只包含那些實現了Visitable介面的物件。
但似乎這還是額外的工作。更糟糕的是,當增加一個新的Visitable型別如VisitableInteger時,會發生什麼呢?這是Visitor模式的一個重大缺陷。如果想增加一個新的Visitable物件,就必須修改Visitor介面,然後對每一個Visitor實現類中的相應的方法一一實現。你可以用一個帶預設空操作的Visitor抽象基類來代替介面。那就很象Java GUI中的Adapter類。那個方法的問題在於,它需要佔用單繼承;而你往往想保留單繼承,讓它用於其它什麼東西,比如繼承StringWriter。那個方法還有限制,它只能夠成功訪問Visitable物件。
幸運的是,Java可以讓Visitor模式更靈活,使得你可以隨心所欲地增加Visitable物件。怎麼做?答案是,使用Reflection。比如,可以設計這樣一個ReflectiveVisitor介面,它只需要一個方法:
public interface ReflectiveVisitor {
public void visit(Object o);
}
就這樣,很簡單。至於Visitable,還是和前面一樣,我過一會兒再說。現在先用Reflection來實現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
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);
}
}
}
現在不需要Visitable包裝類。僅僅只是呼叫visit(),請求就會分發到正確的方法上。很不錯的一點是,只要認為適合,visit()就可以分發。這並非必須使用reflection--它可以使用其它完全不同的機制。
新的PrintVisitor中,有針對Collection,String和Float而寫的方法,但然後它又在catch語句中捕捉所有未處理的型別。你要擴充套件visit()方法,使得它也能夠處理所有的父類。首先,得增加一個新方法,稱為getMethod(Class c),它返回的是要呼叫的方法;為了找到這個相匹配的方法,先在類c的所有父類中尋找,然後在類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;
}
看起來有些複雜,其實不然。實際上,它只是根據傳進來的類名去尋找相應的方法而已。如果沒找到,就在父類中找;還沒找到,再到介面中找。最後,就拿visitObject()作為預設。
注意,為了照顧那些熟悉傳統Visitor模式的讀者,我對方法的名稱採用了傳統的命名方式。但正如你們一些人所注意到的,把所有的方法命名為 "visit" 然後讓引數型別作為區分會更高效。但這樣做的話,你得把主visit(Object o)方法的名字改為dispatch(Object o)之類。否則,就沒有一個預設方法可用了,你就得在呼叫visit(Object o)時將型別轉換為Object,以保證visit採用的是正確的呼叫方式。
現在可以修改visit()方法,以利用getMethod():
public void visit(Object object) {
try {
Method method = getMethod(getClass(), object.getClass());
method.invoke(this, new Object[] {object});
} catch (Exception e) { }
}
現在,visitor物件的功能強大多了。你可以傳進任何物件,並且有某個方法處理它。另外一個好處是,還有一個預設方法visitObject(Object o),它可以捕捉任何未知的物件。再多花點工夫,你還可以寫出一個visitNull()方法。
我在上面對Visitable介面避而不談自有原因。傳統Visitor模式的另一個好處是,它允許Visitable物件來控制對物件結構的訪問。例如,假設有一個實現了Visitable的TreeNode物件,你可以讓一個accept()方法來遍歷它的左右節點:
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);
}
如果已經實現了一個Visitable物件結構,可以保留callAccept()方法並使用Visitable控制訪問。如果想在visitor中訪問結構,只需改寫callAccept()方法,使之什麼也不做。
想讓數個不同的訪問者對同一個物件集合進行訪問時,Visitor模式可以發揮它的強大作用。假設已經有一個直譯器,一箇中綴寫作器,一個字尾寫作器,一個寫作器和一個寫作器,它們都作用在同一個物件集合上。那麼,也可以很容易地為相同的物件集合寫出一個字首寫作器和一個P寫作器。另外,這些寫作器可以正常地和它們所不知道的物件工作;當然,如果願意,也可以讓它們丟擲異常。
結論
透過使用Java Reflection,你可以增強Visitor模式,使之具有操作物件結構的強大功能,並在增加新Visitable型別方面提供靈活性。希望你在以後的中能夠應用這一模式。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-990822/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 設計模式、用Delphi描述-->Visitor模式 (轉)設計模式
- 用Java實現的設計模式系列(1)-Factory (轉)Java設計模式
- JAVA設計模式之 訪問者模式【Visitor Pattern】Java設計模式
- Java ReflectionJava
- Visitor模式加深理解模式
- Groovy探索 Visitor模式模式
- 設計模式、用Delphi實現---->Builder模式 (轉)設計模式UI
- 設計模式、用Delphi實現---->Singleton 模式 (轉)設計模式
- Visitor模式和Observer觀察者模式模式Server
- 看visitor模式的感受模式
- 用 Java 實現回撥例程 (轉)Java
- 用SERVICE LOCATOR 模式實現命名訪問 (轉)模式
- JAVA特性 之 反射(Reflection)Java反射
- Visitor Pattern Introduction (轉)
- C++設計模式 - 訪問器模式(Visitor)C++設計模式
- Java實現工廠模式Java模式
- 從零實現Vue的元件庫(六)- Hover-Tip 實現Vue元件
- 用JAVA實現Undo、Redo,Copy、Paste、Cut (轉)JavaAST
- MVC模式在Java Web應用程式中的實現MVC模式JavaWeb
- 設計模式-Java實現單例模式設計模式Java單例
- C#設計模式系列:訪問者模式(Visitor)C#設計模式
- 設計模式--訪問者模式Visitor(行為型)設計模式
- 用Lambda實現模板模式模式
- Go語言中反射包的實現原理(The Laws of Reflection)Go反射
- 用 System.Reflection.Emit 來自動生成呼叫儲存過程的實現MIT儲存過程
- 用udp方式進行聊天的java實現. (轉)UDPJava
- Java設計模式實現之二--策略模式Java設計模式
- 九、GO 程式設計模式:K8S VISITOR 模式Go程式設計設計模式K8S
- 設計模式的征途—16.訪問者(Visitor)模式設計模式
- 如何通過反射(Reflection)+註解(Attribute)來實現陣列轉化為物件反射陣列物件
- 淺談Java —— Reflection機制(一)Java
- Oracle 管理的小tip.()(轉)Oracle
- java 橋接模式實現程式碼Java橋接模式
- 設計模式 - java程式碼實現單例模式設計模式Java單例
- Java 實現 markdown轉HtmlJavaHTML
- Singleton模式之Delphi實現 (轉)模式
- MVC模式的PHP實現(3) (轉)MVC模式PHP
- 完成C++不能做到的事 - Visitor模式C++模式