「補課」進行時:設計模式(18)——訪問者模式

1. 前文彙總

「補課」進行時:設計模式系列java

2. 引言

訪問者模式也能夠說是全部設計模式中最難的一種設計模式了,固然咱們日常也不多會用到它。設計模式的做者是這麼評價訪問者模式的:大多狀況下,你並不須要使用訪問者模式,可是一旦須要使用它時,那就真的須要使用了。設計模式

3. 一個簡單的示例

又快到年末, CEO 和 CTO 開始評定員工一年的工做績效,員工分爲工程師和經理, CTO 關注工程師的代碼量、經理的新產品數量; CEO 關注的是工程師的KPI和經理的KPI以及新產品數量。數據結構

因爲 CEO 和 CTO 對於不一樣員工的關注點是不同的,這就須要對不一樣員工類型進行不一樣的處理。訪問者模式此時能夠派上用場了。dom

首先定義一個員工基類 Staff :ide

public abstract class Staff {
    public String name;
    // 員工KPI
    public int kpi;

    public Staff(String name) {
        this.name = name;
        kpi = new Random().nextInt(10);
    }
    // 核心方法,接受Visitor的訪問
    abstract void accept(Visitor visitor);
}

Staff 類定義了員工基本信息及一個 accept() 方法, accept() 方法表示接受訪問者的訪問,由子類具體實現。函數

Visitor 是個接口,傳入不一樣的實現類,可訪問不一樣的數據。this

下面是工程師和經理的具體實現類:設計

public class Engineer extends Staff {
    public Engineer(String name) {
        super(name);
    }
    @Override
    void accept(Visitor visitor) {
        visitor.visit(this);
    }
    // 工程師一年的代碼數量
    public int getCodeLines() {
        return new Random().nextInt(10 * 10000);
    }
}

public class Manager extends Staff {
    public Manager(String name) {
        super(name);
    }
    @Override
    void accept(Visitor visitor) {
        visitor.visit(this);
    }
    // 一年作的產品數量
    public int getProducts() {
        return new Random().nextInt(10);
    }
}

工程師是代碼數量,經理是產品數量,他們的職責不同,也就是由於差別性,才使得訪問模式可以發揮它的做用。code

下面是 Visitor 接口的定義:orm

public interface Visitor {
    // 訪問工程師類型
    void visit(Engineer engineer);
    // 訪問經理類型
    void visit(Manager manager);
}

Visitor 聲明瞭兩個 visit 方法,分別是對工程師和經理對訪問函數。

接下來定義兩個具體的訪問者: CEO 和 CTO 。

public class CEOVisitor implements Visitor {
    @Override
    public void visit(Engineer engineer) {
        System.out.println("工程師: " + engineer.name + ", KPI: " + engineer.kpi);
    }

    @Override
    public void visit(Manager manager) {
        System.out.println("經理: " + manager.name + ", KPI: " + manager.kpi + ", 新產品數量: " + manager.getProducts());
    }
}

public class CTOVisitor implements Visitor {
    @Override
    public void visit(Engineer engineer) {
        System.out.println("工程師: " + engineer.name + ", 代碼行數: " + engineer.getCodeLines());
    }

    @Override
    public void visit(Manager manager) {
        System.out.println("經理: " + manager.name + ", 產品數量: " + manager.getProducts());
    }
}

接着是一個報表類,公司的 CEO 和 CTO 經過這個報表查看全部員工的業績:

public class BusinessReport {
    private List<Staff> mStaffs = new LinkedList<>();
    public BusinessReport() {
        mStaffs.add(new Manager("經理-A"));
        mStaffs.add(new Engineer("工程師-A"));
        mStaffs.add(new Engineer("工程師-B"));
        mStaffs.add(new Manager("經理-B"));
        mStaffs.add(new Engineer("工程師-C"));
    }
    /**
     * 爲訪問者展現報表
     * @param visitor 公司高層,如 CEO、CTO
     */
    public void showReport(Visitor visitor) {
        for (Staff staff : mStaffs) {
            staff.accept(visitor);
        }
    }
}

最後是一個場景類:

public class Client {
    public static void main(String[] args) {
        // 構建報表
        BusinessReport report = new BusinessReport();
        System.out.println("=========== CEO看報表 ===========");
        report.showReport(new CEOVisitor());
        System.out.println("=========== CTO看報表 ===========");
        report.showReport(new CTOVisitor());
    }
}

執行結果以下:

=========== CEO看報表 ===========
經理: 經理-A, KPI: 7, 新產品數量: 8
工程師: 工程師-A, KPI: 6
工程師: 工程師-B, KPI: 3
經理: 經理-B, KPI: 4, 新產品數量: 4
工程師: 工程師-C, KPI: 2
=========== CTO看報表 ===========
經理: 經理-A, 產品數量: 6
工程師: 工程師-A, 代碼行數: 61280
工程師: 工程師-B, 代碼行數: 10353
經理: 經理-B, 產品數量: 5
工程師: 工程師-C, 代碼行數: 65827

4. 訪問者模式

4.1 定義

訪問者模式(Visitor Pattern) 是一個相對簡單的模式, 其定義以下:

Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates. (封裝一些做用於某種數據結構中的各元素的操做, 它能夠在不改變數據結構的前提下定義做用於這些元素的新的操做。 )

4.2 通用類圖

  • Visitor 抽象訪問者:抽象類或者接口,聲明訪問者能夠訪問哪些元素。
  • ConcreteVisitor 具體訪問者:它影響訪問者訪問到一個類後該怎麼幹, 要作什麼事情。
  • Element 抽象元素:接口或者抽象類,聲明接受哪一類訪問者訪問。
  • ConcreteElement 具體元素:實現方法。
  • ObjectStruture 結構對象:元素產生者,通常容納在多個不一樣類、不一樣接口的容器。

4.3 通用代碼

抽象元素:

public abstract class Element {
    // 定義業務邏輯
    abstract void doSomething();
    // 定義容許訪問角色
    abstract void accept(IVisitor visitor);
}

具體元素:

public class ConcreteElement1 extends Element{
    @Override
    void doSomething() {

    }

    @Override
    void accept(IVisitor visitor) {
        visitor.visit(this);
    }
}

public class ConcreteElement2 extends Element{
    @Override
    void doSomething() {

    }

    @Override
    void accept(IVisitor visitor) {
        visitor.visit(this);
    }
}

抽象訪問者:

public interface IVisitor {
    void visit(ConcreteElement1 ele1);
    void visit(ConcreteElement2 ele2);
}

具體訪問者:

public class Visitor implements IVisitor{
    @Override
    public void visit(ConcreteElement1 ele1) {
        ele1.doSomething();
    }

    @Override
    public void visit(ConcreteElement2 ele2) {
        ele2.doSomething();
    }
}

結構對象:

public class ObjectStruture {
    public static Element createElement() {
        Random random = new Random();
        if (random.nextInt(100) > 50) {
            return new ConcreteElement1();
        } else {
            return new ConcreteElement2();
        }
    }
}

場景類:

public class Client {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Element e1 = ObjectStruture.createElement();
            e1.accept(new Visitor());
        }
    }
}

4.4 優勢

  1. 各角色職責分離,符合單一職責原則。

    經過UML類圖和上面的示例能夠看出來,Visitor、ConcreteVisitor、Element 、ObjectStructure,職責單一,各司其責。

  2. 具備優秀的擴展性。

    若是須要增長新的訪問者,增長實現類 ConcreteVisitor 就能夠快速擴展。

  3. 使得數據結構和做用於結構上的操做解耦,使得操做集合能夠獨立變化。

    員工屬性(數據結構)和CEO、CTO訪問者(數據操做)的解耦。

  4. 靈活性。

4.5 缺點

  1. 具體元素對訪問者公佈細節,違反了迪米特原則。

    CEO、CTO須要調用具體員工的方法。

  2. 具體元素變動時致使修改爲本大。

    變動員工屬性時,多個訪問者都要修改。

  3. 違反了依賴倒置原則,爲了達到「區別對待」而依賴了具體類,沒有用來抽象。

    訪問者 visit 方法中,依賴了具體員工的具體方法。

相關文章
相關標籤/搜索