一、定義
封裝某些作用於某種數據結構中各元素的操作,它可以在不改變數據結構的前提下定義作用於這些元素的新的操作。
二、類圖
三、代碼例子
先建一個元素接口IPerson,定義一個方法accept ,爲了讓訪問者能訪問到自己
public interface IPersonService {
void accept(IVisitorService action);
String getName();
}
- 實現具體的元素(這裏的元素一般是固定的,這個也是訪問者模式的適應場景,如果元素經常變動,不適合訪問者模式,這是重點,敲黑板),我這邊選中用男人和女人做爲元素,一般系統沒有第三種
public class Man implements IPersonService {
@Override
public void accept(IActionService action) {
action.visitor(this);
}
@Override
public String getName() {
return "Man";
}
}
public class Women implements IPersonService {
@Override
public void accept(IActionService action) {
action.visitor(this);
}
@Override
public String getName() {
return "Women";
}
}
- 創建訪問者(這裏要注意,就是有幾個元素,都會有一個對應的處理方法,這裏也就是 有一點和開閉原則 不符合的地方吧,這裏是重點,敲黑板)
public interface IVisitorService {
void visitorMan(Man man);
void visitorWomen(Women man);
}
- 具體的訪問者,我這邊弄了兩個訪問者,visitor1 和visitor2
public class Visitor1 implements IVisitorService {
@Override
public void visitorMan(Man man) {
System.out.println("Visitor1 " + man.getName());
}
@Override
public void visitorWomen(Women woman) {
System.out.println("Visitor1 " + woman.getName());
}
}
public class Visitor2 implements IVisitorService {
@Override
public void visitorMan(Man man) {
System.out.println("Visitor2 " + man.getName());
}
@Override
public void visitorWomen(Women man) {
System.out.println("Visitor2 " + man.getName());
}
}
- 創建一個 ObjectStructure,用於枚舉元素:
public class ObjectStructure {
List<IPersonService> list = Lists.newArrayList();
/**
* 增加
*
* @param action
*/
void add(IPersonService action) {
this.list.add(action);
}
/**
* 移除
*
* @param action
*/
void remove(IPersonService action) {
this.list.remove(action);
}
/**
* @param action
*/
public void display(IVisitorService action) {
list.stream().forEach(e -> {
e.accept(action);
});
}
}
6 測試一下,建個client:
public class client {
public static void main(String[] args) {
ObjectStructure obj = new ObjectStructure();
obj.add(new Man());
obj.add(new Women());
Visitor1 v1 = new Visitor1();
obj.display(v1);
Visitor2 v2 = new Visitor2();
obj.display(v2);
}
}
4、改進,加入反射
4.1接口加入反射
上面也說了,增加元素時比較麻煩嘛,設計模式的書裏也提到過有一定的現在,這裏可以用反射的方式,根據具體的元素執行具體的方法嘛(
參照文章
參考的這邊文章 https://www.javaworld.com/article/2077602/java-tip-98--reflect-on-the-visitor-design-pattern.html)
思路:接口裏面增加一個visit 方法,根據具體的類型,調用具體的執行方法,代碼如下:
public interface IVisitorService {
void visitorMan(Man man);
void visitorWomen(Women man);
/**
* 這裏是通過反射,選擇具體的執行方法
* @param obj
*/
void visitor(Object obj);
}
具體的visitor 1(visitor2 類的代碼就不貼了,和 visitor1 一樣的) (敲黑板,劃重點,這是匹配規則時,用了一個visitor 作爲前綴,這裏需要統一一個命名規則)如下:
public class Visitor1 implements IVisitorService {
public void visitorMan(Man man) {
System.out.println("Visitor1 " + man.getName());
}
public void visitorWomen(Women woman) {
System.out.println("Visitor1 " + woman.getName());
}
@Override
public void visitor(Object obj) {
String className = obj.getClass().getName();
String methodName = "visitor" + className.substring(className.lastIndexOf(".") + 1);
try {
Method method = getClass().getMethod(methodName, new Class<?>[] {obj.getClass()});
method.invoke(this, new Object[]{obj});
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
接下來所有的 元素裏面的accept ,調用的是 visit() 方法:
public class Man implements IPersonService {
@Override
public void accept(IVisitorService action) {
action.visitor(this);
}
@Override
public String getName() {
return "Man";
}
}
小結一下:這樣就可以解決,增加元素時IVisitor 裏面類型定死的問題了,直接在實現類裏面定義即可。
4.2 再改進
我感覺 在接口裏面定義一個 visitor 方法,每一個實現類都需要實現,只要遵循規則,感覺每次都要實現太麻煩了,所以我提出再抽出一個 基礎類,把visitor(Object obj) 方法放在裏面,如果需要改動,也可以在實現類裏面重寫,如果不需要,直接默認使用
public abstract class BaseAction implements IVisitorService{
public void visitor(Object obj) {
String className = obj.getClass().getName();
String methodName = "visitor" + className.substring(className.lastIndexOf(".") + 1);
try {
Method method = getClass().getMethod(methodName, new Class<?>[] {obj.getClass()});
method.invoke(this, new Object[]{obj});
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
具體的訪問者只要繼承 BaseAction 即可,其他不用動。如下爲 Visitor1 的代碼(Visitor2 代碼幾乎一樣),這樣一看,都簡潔
public class Visitor1 extends BaseAction{
public void visitorMan(Man man) {
System.out.println("Visitor1 " + man.getName());
}
public void visitorWomen(Women woman) {
System.out.println("Visitor1 " + woman.getName());
}
}
到此,訪問者設計模式 梳理差不多了,如果有哪裏對不,請指出,謝謝。