這是我參與8月更文挑戰的第6天,活動詳情查看: 8月更文挑戰java
😁 週三已到,週末還會遠嗎?繼續啃設計模式之美,本文對應設計模式與範式:行爲型(68-69),訪問者模式 (Visitor Pattern),用於 解耦對象結構與對象操做
。編程
其難點在於代碼實現比較複雜,由於大部分面向對象編程語言中是 靜態綁定 的。調用類的哪一個重載函數,是在 編譯期由函數聲明類型決定,而非 運行時根據參數實際類型決定 的。設計模式
代碼實現難理解,在項目中應用此模式可能致使可讀性較差,沒有特別必要的狀況,建議不要使用這種模式~markdown
固然,瞭解下也無妨,萬一接盤時真遇到了別人用這種模式,也不至於一臉懵逼~數據結構
Tips:二手知識加工不免有所紕漏,感興趣有時間的可自行查閱原文,謝謝。app
原始定義編程語言
容許在 運行時 將一個或多個操做應用於一組對象,將操做與對象結構分離。ide
簡單點說函數
一組對象,對象結構能夠各不相同,但必須以某個或一組操做做爲鏈接的中心點,即:post
以行爲(某個操做) 做爲擴展對象的出發點,在不改變已有類的功能前提下進行批量擴展。
以汽車結構爲例,裏面包含了引擎,車身等,不一樣角色的人能夠對這些結構進行不一樣的訪問,如:
不用訪問者,代碼實現一波:
public abstract class AbstractVisitor {
protected String name;
public AbstractVisitor(String name) { this.name = name; }
abstract void visitorEngine();
abstract void visitorBody();
}
public class DriverVisitor extends AbstractVisitor {
public DriverVisitor(String name) { super(name); }
@Override void visitorEngine() { System.out.println(name + " → 查看引擎"); }
@Override void visitorBody() { System.out.println(name + " → 查看車身"); }
}
public class CleanerVisitor extends AbstractVisitor {
public CleanerVisitor(String name) { super(name); }
@Override void visitorEngine() { System.out.println(name + " → 清洗引擎"); }
@Override void visitorBody() { System.out.println(name + " → 清洗車身"); }
}
public class RepairVisitor extends AbstractVisitor {
public RepairVisitor(String name) { super(name); }
@Override void visitorEngine() { System.out.println(name + " → 修理引擎"); }
@Override void visitorBody() { System.out.println(name + " → 修理車身"); }
}
// 測試用例
public class Test {
public static void main(String[] args) {
List<AbstractVisitor> visitors = new ArrayList<>();
visitors.add(new DriverVisitor("傑哥"));
visitors.add(new CleanerVisitor("老王"));
visitors.add(new RepairVisitor("大錘"));
for(AbstractVisitor visitor: visitors) {
visitor.visitorEngine();
visitor.visitorBody();
}
}
}
複製代碼
代碼運行結果以下:
能夠,但存在問
拆分解耦,把業務操做與具體的數據結構解耦,設計成獨立的類,用訪問者模式對象上述代碼重構:
// 抽象訪問者
public abstract class AbstractVisitor {
protected String name;
public AbstractVisitor(String name) { this.name = name; }
}
// 具體訪問者
public class DriverVisitor extends AbstractVisitor {
public DriverVisitor(String name) { super(name); }
}
public class CleanerVisitor extends AbstractVisitor {
public CleanerVisitor(String name) { super(name); }
}
public class RepairVisitor extends AbstractVisitor {
public RepairVisitor(String name) { super(name); }
}
// 對象結構
public class Car {
public void visitorEngine(DriverVisitor visitor) {
System.out.println(visitor.name + " → 查看引擎");
}
public void visitorEngine(CleanerVisitor visitor) {
System.out.println(visitor.name + " → 清洗引擎");
}
public void visitorEngine(RepairVisitor visitor) {
System.out.println(visitor.name + " → 修理引擎");
}
public void visitorBody(DriverVisitor visitor) {
System.out.println(visitor.name + " → 查看車身");
}
public void visitorBody(CleanerVisitor visitor) {
System.out.println(visitor.name + " → 清洗車身");
}
public void visitorBody(RepairVisitor visitor) {
System.out.println(visitor.name + " → 修理車身");
}
}
// 測試用例
public class Test {
public static void main(String[] args) {
List<AbstractVisitor> visitors = new ArrayList<>();
visitors.add(new DriverVisitor("傑哥"));
visitors.add(new CleanerVisitor("老王"));
visitors.add(new RepairVisitor("大錘"));
Car car = new Car();
for(AbstractVisitor visitor: visitors) {
// 此處編譯不經過
car.visitorEngine(visitor);
car.visitorBody(visitor);
}
}
}
複製代碼
上述代碼理論上是可行,利用函數重載,根據參數類型調用對應方法,實際上倒是編譯都過不了!
緣由是:
Java中的
函數重載
是一種靜態綁定
,編譯時並不能獲取對象的實際類型
,而是根據聲明類型
執行聲明類型對應的方法。
解法就是:將行爲/業務抽象成單獨的類,函數傳入不一樣的訪問者,根據不一樣的訪問者入參執行對應操做,訪問者從主動變成被動,以此規避了編譯失敗問題。接着代碼實現一波:
訪問角色
,引擎和車身,傳入不一樣的訪問者,執行不一樣的操做:
// 抽象訪問角色類
public interface Visit {
void visit(DriverVisitor visitor);
void visit(CleanerVisitor visitor);
void visit(RepairVisitor visitor);
}
// 訪問角色具體實現類
public class Engine implements Visit {
@Override public void visit(DriverVisitor visitor) {
System.out.println(visitor.name + "→ 查看引擎");
}
@Override public void visit(CleanerVisitor visitor) {
System.out.println(visitor.name + "→ 清洗引擎");
}
@Override public void visit(RepairVisitor visitor) {
System.out.println(visitor.name + "→ 修理引擎");
}
}
public class Body implements Visit {
@Override public void visit(DriverVisitor visitor) {
System.out.println(visitor.name + "→ 查看車身");
}
@Override public void visit(CleanerVisitor visitor) {
System.out.println(visitor.name + "→ 清洗車身");
}
@Override public void visit(RepairVisitor visitor) {
System.out.println(visitor.name + "→ 修理車身");
}
}
複製代碼
再接着是 訪問者
,定義全部 訪問角色 的訪問操做:
// 抽象訪問者類
public abstract class AbstractVisitor {
protected String name;
public AbstractVisitor(String name) { this.name = name; }
abstract void visitorEngine(Engine engine);
abstract void visitorBody(Body body);
}
// 具體訪問者類
public class DriverVisitor extends AbstractVisitor {
public DriverVisitor(String name) { super(name); }
@Override void visitorEngine(Engine engine) { engine.visit(this); }
@Override void visitorBody(Body body) { body.visit(this); }
}
public class CleanerVisitor extends AbstractVisitor {
public CleanerVisitor(String name) { super(name); }
@Override void visitorEngine(Engine engine) { engine.visit(this); }
@Override void visitorBody(Body body) { body.visit(this); }
}
public class RepairVisitor extends AbstractVisitor {
public RepairVisitor(String name) { super(name); }
@Override void visitorEngine(Engine engine) { engine.visit(this); }
@Override void visitorBody(Body body) { body.visit(this); }
}
複製代碼
接着上測試用例:
public class Test {
public static void main(String[] args) {
List<AbstractVisitor> visitors = new ArrayList<>();
visitors.add(new DriverVisitor("傑哥"));
visitors.add(new CleanerVisitor("老王"));
visitors.add(new RepairVisitor("大錘"));
// 實例化訪問角色
Engine engine = new Engine();
Body body = new Body();
for(AbstractVisitor visitor: visitors) {
visitor.visitorEngine(engine);
visitor.visitorBody(body);
}
}
}
複製代碼
代碼運行結果同上,能夠,但還存在添加新業務,每一個訪問者都要改動的問題,還要再改改~
// 抽象訪問者類
public class RepairVisitor extends AbstractVisitor {
public RepairVisitor(String name) { super(name); }
@Override void accept(Visit visit) { visit.visit(this); }
}
// 具體訪問者類
public class DriverVisitor extends AbstractVisitor {
public DriverVisitor(String name) { super(name); }
@Override void accept(Visit visit) { visit.visit(this); }
}
public class CleanerVisitor extends AbstractVisitor {
public CleanerVisitor(String name) { super(name); }
@Override void accept(Visit visit) { visit.visit(this); }
}
public class RepairVisitor extends AbstractVisitor {
public RepairVisitor(String name) { super(name); }
@Override void accept(Visit visit) { visit.visit(this); }
}
// 測試用例
public class Test {
public static void main(String[] args) {
List<AbstractVisitor> visitors = new ArrayList<>();
visitors.add(new DriverVisitor("傑哥"));
visitors.add(new CleanerVisitor("老王"));
visitors.add(new RepairVisitor("大錘"));
// 實例化訪問角色
Engine engine = new Engine();
Body body = new Body();
for(AbstractVisitor visitor: visitors) {
visitor.accept(engine);
visitor.accept(body);
}
}
}
複製代碼
改完後,此時咱們要增長一個訪問輪胎的業務,只需讓其實現Visit接口,實例化後直接accept()便可,不用改動訪問者角色代碼,妙啊!!!
順帶帶出UML類圖、組成角色、使用場景及優缺點:
使用場景
優勢:
缺點
哪一個對象的方法
,根據 對象的運行時類型
決定;對象的哪一個方法
,根據 方法參數的編譯時類型
決定;哪一個對象的方法
,根據 對象的運行時類型
決定;對象的哪一個方法
,根據 方法參數的運行時類型
決定;Java函數重載,調用哪一個重載函數,取決於入參的聲明類型(編譯器時類型),因此它只支持單分派。
上面例子就是使用觀察模式間接實現雙分派,固然也可使用其餘方式實現,如前面學的工廠方法模式~
以上內容就是本節的所有內容,謝謝~