博客原文地址:折騰Java設計模式之訪問者模式html
Represent an operation to be performed on the elements of an object structure. Visitor lets a new operation be defined without changing the classes of the elements on which it operates.java
訪問者模式的目的是封裝一些施加於某種數據結構元素之上的操做。一旦這些操做須要修改的話,接受這個操做的數據結構則能夠保持不變。git
意圖 主要將數據結構與數據操做分離。github
主要解決 穩定的數據結構和易變的操做耦合問題。設計模式
什麼時候使用 須要對一個對象結構中的對象進行不少不一樣的而且不相關的操做,而須要避免讓這些操做"污染"這些對象的類,使用訪問者模式將這些封裝到類中。微信
Visitor(抽象訪問者):抽象訪問者爲對象結構中每個具體元素類ConcreteElement聲明一個訪問操做,從這個操做的名稱或參數類型能夠清楚知道須要訪問的具體元素的類型,具體訪問者須要實現這些操做方法,定義對這些元素的訪問操做。數據結構
ConcreteVisitor(具體訪問者):具體訪問者實現了每一個由抽象訪問者聲明的操做,每個操做用於訪問對象結構中一種類型的元素。ide
Element(抽象元素):抽象元素通常是抽象類或者接口,它定義一個accept()方法,該方法一般以一個抽象訪問者做爲參數。函數
ConcreteElement(具體元素):具體元素實現了accept()方法,在accept()方法中調用訪問者的訪問方法以便完成對一個元素的操做。this
ObjectStructure(對象結構):對象結構是一個元素的集合,它用於存放元素對象,而且提供了遍歷其內部元素的方法。它能夠結合組合模式來實現,也能夠是一個簡單的集合對象,如一個List對象或一個Set對象。
使用場景: 一、對象結構中對象對應的類不多改變,但常常須要在此對象結構上定義新的操做。 二、須要對一個對象結構中的對象進行不少不一樣的而且不相關的操做,而須要避免讓這些操做"污染"這些對象的類,也不但願在增長新操做時修改這些類。
UML類圖和時序圖
在類圖中能夠看出,ElementA實現了接口Element的accept(visitor)方法,而經過visitor.visitElementA(this),相同visitor1類經過實現visitElementA(ElementA a)方法與ElementA關聯。相同的ElementB亦是如此原理。
右上角的時序圖,Client對象有一組Element的數據結構,經過循環對每一個元素Element調用accept(visitor)方法,例如先是ElementA調用accept(visitor),實際上就是調用visitor1的visitElementA(A),一樣狀況對ElementB。
更加清晰的類圖以下
//抽象元素
public interface Element {
void accept(ElementVisitor visitor);
}
//具體元素-車輪
@Data
@AllArgsConstructor
public class Wheel implements Element {
private String name;
@Override
public void accept(ElementVisitor visitor) {
visitor.visit(this);
}
}
//具體元素-車身
public class Body implements Element {
@Override
public void accept(ElementVisitor visitor) {
visitor.visit(this);
}
}
//具體元素-引擎
public class Engine implements Element {
@Override
public void accept(ElementVisitor visitor) {
visitor.visit(this);
}
}
//具體元素-整車
public class Car implements Element {
public void accept(final ElementVisitor visitor) {
visitor.visit(this);
}
}
//抽象訪問者
public interface ElementVisitor {
void visit(Body body);
void visit(Engine engine);
void visit(Wheel wheel);
void visit(Car car);
}
//具體的一個訪問者,純打印
@Slf4j
public class DoElementVisitor implements ElementVisitor {
@Override
public void visit(Body body) {
log.info("Moving my body");
}
@Override
public void visit(Engine engine) {
log.info("Starting my engine");
}
@Override
public void visit(Wheel wheel) {
log.info("Kicking my " + wheel.getName() + " wheel");
}
@Override
public void visit(Car car) {
log.info("Starting my car");
}
}
//單獨還定義對象結構,其實徹底就可使用列表就能夠
@Data
public class ElementStructure {
private List<Element> list = Lists.newArrayList();
public void addElement(Element element){
list.add(element);
}
public void accept(ElementVisitor visitor) {
for (Element elem : list) {
elem.accept(visitor);
}
}
}
複製代碼
上述就是針對訪問者模式作的一個對於汽車零件的一個打印效果。
public class ClientWithVisitor {
public static void main(String[] args) {
ElementStructure structure = new ElementStructure();
structure.addElement(new Wheel("front left"));
structure.addElement(new Wheel("front right"));
structure.addElement(new Wheel("back left"));
structure.addElement(new Wheel("back right"));
structure.addElement(new Body());
structure.addElement(new Engine());
structure.addElement(new Car());
structure.accept(new DoElementVisitor());
}
}
@Slf4j
public class ClientWithoutVisitor {
public static void main(String[] args) {
ElementStructure structure = new ElementStructure();
structure.addElement(new Wheel("front left"));
structure.addElement(new Wheel("front right"));
structure.addElement(new Wheel("back left"));
structure.addElement(new Wheel("back right"));
structure.addElement(new Body());
structure.addElement(new Engine());
structure.addElement(new Car());
structure.getList().forEach(e -> {
if (e instanceof Body) {
log.info("Moving my body");
} else if (e instanceof Engine) {
log.info("Starting my engine");
} else if (e instanceof Car) {
log.info("Starting my car");
} else if (e instanceof Wheel) {
log.info("Kicking my " + ((Wheel)e).getName() + " wheel");
}
});
}
}
複製代碼
打印結果都是同樣的
在上面的例子中分別用了訪問者模式和非訪問者模式兩種方法。
一、使用VIsitor的好處一目瞭然,當須要修改某些元素的業務邏輯時,只須要修改Visitor類中相對應的操做函數便可。例如假設要修改Wheel的邏輯,只須要修改Visitor的visit(Wheel wheel)方法便可。
二、假設咱們又須要新增一個汽車元素天窗的話,只須要在visitor中添加新的接口以處理新元素,而別的元素能夠保持不動。 違背開閉原則。
三、當咱們須要添加新的業務操做,只須要添加新的具體訪問者,其餘的依舊能夠保持不變。符合開閉原則。
一樣,有好處也就有缺陷,由於邏輯在visitor裏面,全部visitor和Element高度耦合,一樣針對visit方法返回類型,須要設計的優雅,如若否則,後期一旦修改返回類型,影響的範圍就廣,全部訪問者接口和實現都波及到。訪問者模式要求訪問者對象訪問並調用每個元素對象的操做,這意味着元素對象有時候必須暴露一些本身的內部操做和內部狀態,不然沒法供訪問者訪問,這一點跟迪米特法則和依賴倒置原則相違背。
總的而言,訪問者模式的使用條件較爲苛刻,自己結構也較爲複雜,所以在實際應用中使用頻率不是特別高。
提供一個方便的可維護的方式來操做一組對象。它使得你在不改變操做的對象前提下,能夠修改或者擴展對象的行爲。
javax.lang.model.element.Element and javax.lang.model.element.ElementVisitor
javax.lang.model.type.TypeMirror and javax.lang.model.type.TypeVisitor
JAVA設計模式(23):行爲型-訪問者模式(Visitor)