把書讀薄 | 《設計模式之美》設計模式與範式(行爲型-訪問者模式)

這是我參與8月更文挑戰的第6天,活動詳情查看: 8月更文挑戰java

0x0、引言

😁 週三已到,週末還會遠嗎?繼續啃設計模式之美,本文對應設計模式與範式:行爲型(68-69),訪問者模式 (Visitor Pattern),用於 解耦對象結構與對象操做編程

其難點在於代碼實現比較複雜,由於大部分面向對象編程語言中是 靜態綁定 的。調用類的哪一個重載函數,是在 編譯期由函數聲明類型決定,而非 運行時根據參數實際類型決定 的。設計模式

代碼實現難理解,在項目中應用此模式可能致使可讀性較差,沒有特別必要的狀況,建議不要使用這種模式~markdown

固然,瞭解下也無妨,萬一接盤時真遇到了別人用這種模式,也不至於一臉懵逼~數據結構

Tips:二手知識加工不免有所紕漏,感興趣有時間的可自行查閱原文,謝謝。app


0x一、定義

原始定義編程語言

容許在 運行時 將一個或多個操做應用於一組對象,將操做與對象結構分離。ide

簡單點說函數

一組對象,對象結構能夠各不相同,但必須以某個或一組操做做爲鏈接的中心點,即:post

以行爲(某個操做) 做爲擴展對象的出發點,在不改變已有類的功能前提下進行批量擴展。

0x二、寫個簡單例子

以汽車結構爲例,裏面包含了引擎,車身等,不一樣角色的人能夠對這些結構進行不一樣的訪問,如:

  • 司機 → 查看
  • 洗車佬 → 清潔
  • 維修佬 → 檢查維修

不用訪問者,代碼實現一波:

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類圖、組成角色、使用場景及優缺點:

  • Visitor (抽象訪問者) → 定義聲明全部訪問角色的訪問操做;
  • ConcreteVisitor (具體訪問者) → 實現抽象訪問者中實現的全部訪問方法;
  • Element (抽象訪問角色) → 定義接收具體訪問者的方法;
  • ConcreteElement (具體訪問角色) → 實現抽象訪問角色,並實現具體操做;
  • ObjectStructure (對象結構) → 備選,使訪問者可以訪問到對應的元素;

使用場景

  • 對象數據結構相對穩定,而操做卻常常變化;
  • 需將數據結構與不經常使用操做進行分離;
  • 需在運行時動態決定使用哪些對象和方法;

優勢

  • 知足開閉原則和單一職責原則;
  • 可擴展性,增長新的訪問操做及訪問者很是方便;

缺點

  • 不適用於結構常常變化的場景;
  • 具體訪問角色變動時需修改代碼,易引入問題;

0x三、加餐:單分派和雙分派概念

① 單分派

  • 執行 哪一個對象的方法,根據 對象的運行時類型 決定;
  • 執行 對象的哪一個方法,根據 方法參數的編譯時類型 決定;

② 雙分派

  • 執行 哪一個對象的方法,根據 對象的運行時類型 決定;
  • 執行 對象的哪一個方法,根據 方法參數的運行時類型 決定;

Java函數重載,調用哪一個重載函數,取決於入參的聲明類型(編譯器時類型),因此它只支持單分派。

上面例子就是使用觀察模式間接實現雙分派,固然也可使用其餘方式實現,如前面學的工廠方法模式~


以上內容就是本節的所有內容,謝謝~

相關文章
相關標籤/搜索