訪問者模式(學習筆記)

  1. 意圖

  表示一個做用於某對象結構中的各元素的操做。它使你能夠在不改變各元素的類的前提下定義做用於這些元素的新操做java

  2. 動機   

  假如你的團隊開發了一款可以使用巨型圖像中地理信息的應用程序。圖像中的每一個節點既能表明複雜實體(例如一座城市),也能表明更精細的對象(例如工業區和旅遊景點等)。若是節點表明的真實對象之間存在公路,那麼這些節點就會相互鏈接。在程序內部,每一個節點的類型都由其所屬的類來表示,每一個特定的節點則是一個對象。node

         

  一段時間後,接到了實現將圖像導出到XML文件中的任務。這些工做最初看上去很是簡單。你計劃爲每一個節點類添加導出函數,而後遞歸執行圖像中每一個節點的導出函數。解決方案簡單且優雅:使用多態機制可讓導出方法的調用代碼不會和具體的節點類相耦合。但s是系統架構師拒絕批准對已有節點類進行修改。他認爲這些代碼已是產品了,不想冒險對其進行修改,由於修改可能會引入潛在的缺陷。算法

 

  此外,他還質疑在節點類中包含導出XML文件的代碼是否有意義。這些類的主要工做是處理地理數據,導出XML文件的代碼放在這裏並不合適。還有另外一個緣由,那就是在此項任務完成後,營銷部門頗有可能會要求程序提供導出其餘類型文件的功能,或者提出其餘奇怪的要求。這樣你極可能會被迫再次修改這些重要但脆弱的類。設計模式

  訪問者模式建議將新行爲放入一個名爲訪問者的獨立類中,而不是試圖將其整合到已有類中。如今,須要執行操做的原始對象將做爲參數被傳遞給訪問者中的方法,讓方法能訪問對象所包含的一切必要數據。  數據結構

  若是如今該操做能在不一樣類的對象上執行會怎麼樣呢?好比在咱們的示例中,各節點類導出XML文件的實際實現極可能會稍有不一樣。所以,訪問者類能夠定義一組(而不是一個)方法,且每一個方法可接收不一樣類型的參數,以下所示:  架構

class ExportVisitor implements Visitor is
    method doForCity(City c) { ... }
    method doForIndustry(Industry f) { ... }
    method doForSightSeeing(SightSeeing ss) { ... }
    // ...

  問題是,咱們該如何調用這些方法呢?能夠發現,這些方法的簽名各不相同,所以不能使用多態機制。爲了能夠挑選出可以處理特定對象的訪問者方法咱們須要對它的類進行檢查,以下面代碼所示:app

foreach (Node node in graph)
    if (node instanceof City) exportVisitor.doForCity((City) node) if (node instanceof Industry) exportVisitor.doForIndustry((Industry) node) // ... }

  能否使用方法地重載呢?依然不行,由於咱們沒法提早知曉節點對象所屬的類,因此重載機制沒法執行正確的方法ide

  訪問者模式能夠解決這個問題。它使用了一種名爲雙分派(在選擇一個方法的時候,不只僅要根據消息接收者(receiver)的運行時型別(Run time type),還要根據參數的運行時型別(Run time type)。這裏的消息接收者其實就是方法的調用者。具體來說就是,對於消息表達式a.m(b),雙分派可以按照a和b的實際類型爲其綁定對應方法體)的技巧,在不使用累贅的條件語句地狀況下,也能夠執行正確的方法。與其讓客戶端來選擇調用正確版本的方法,不如將對象放在各個節點中,並將訪問者對象做爲參數傳給該對象。因爲該對象知曉其自身的類,所以能更天然地在訪問者中選出正確的方法。它們會 「接收」 一個訪問者並告訴其應執行的訪問者方法函數

// 客戶端代碼
foreach (Node node in graph)
    node.accept(exportVisitor)

// 城市
class City is method accept(Visitor v) is v.doForCity(this) // ... // 工業區 class Industry is method accept(Visitor v) is v.doForIndustry(this) // ...

  咱們最終仍是修改了節點類,但畢竟改動很小,並且在後續進一步添加行爲時無需再次修改代碼。如今,若是咱們抽取出全部訪問者的通用接口,全部已有的節點都能與咱們在程序中引入的任何訪問者交互。若是須要引入與節點相關的某個行爲,你只須要實現一個新的訪問者類便可  ui

  3. 適用性

  • 一個對象結構(如對象樹)包含不少類對象,它們有不一樣的接口,而你想對這些對象實施一些依賴於其具體類的操做
  • 須要對一個對象結構中的對象進行不少不一樣而且不相關的操做,而你想避免讓這些操做污染這些類對象。Visitor使得你能夠將相關操做集中起來定義在一個類中。當對象結構被不少應用共享時,用Visitor模式讓每一個應用僅包含須要用到的操做
  • 當某個行爲僅在類層次結構中的一些類中有意義,而在其餘類中沒有意義時,可以使用該模式

  4. 結構

                                       

  5. 效果

  1. 訪問者模式使得易於增長新的操做(開閉原則)

  2. 訪問者集中相關的操做而分離無關的操做(單一職責原則)

  3. 增長新的ConcreteElement類很困難    Visitor模式使得難以增長新的Element的子類。每添加一個新的ConcreteElement都要在Visitor中添加一個新的抽象操做,並在每個ConcreteVisitor類中實現相應的操做。因此在使用訪問者模式時考慮的關鍵問題是系統的哪一個部分會常常變化,是做用於對象結構上的算法仍是構成該結構的給個對象的類。若是老是有新的ConcreteElement類加進來,Visitor類層次將變得難以維護

  4. 在訪問者同某個元素進行交互時,它們可能沒有訪問元素私有成員變量和方法的必要權限

  6. 代碼實現    

  在本例中 咱們但願將一系列幾何形狀導出爲 XML 文件 重點在於咱們不但願直接修改形狀代碼 或者至少能確保最小程度的修改

  shapes/Shape.java: 通用形狀接口

package visitor.shapes;

import visitor.visitor.Visitor;

/**
 * @author GaoMing
 * @date 2021/7/26 - 17:23
 */
public interface Shape {
    void move(int x, int y);
    void draw();
    String accept(Visitor visitor);
}

  shapes/Dot.java: 點

package visitor.shapes;

import visitor.visitor.Visitor;

/**
 * @author GaoMing
 * @date 2021/7/26 - 17:24
 */
public class Dot implements Shape{
    private int id;
    private int x;
    private int y;

    public Dot() {
    }

    public Dot(int id, int x, int y) {
        this.id = id;
        this.x = x;
        this.y = y;
    }

    @Override
    public void move(int x, int y) {
        // move shape
    }

    @Override
    public void draw() {
        // draw shape
    }

    @Override
    public String accept(Visitor visitor) {
        return visitor.visitDot(this);
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public int getId() {
        return id;
    }
}

  shapes/Circle.java: 圓形

package visitor.shapes;

import visitor.visitor.Visitor;

/**
 * @author GaoMing
 * @date 2021/7/26 - 17:25
 */
public class Circle extends Dot{
    private int radius;

    public Circle(int id, int x, int y, int radius) {
        super(id, x, y);
        this.radius = radius;
    }

    @Override
    public String accept(Visitor visitor) {
        return visitor.visitCircle(this);
    }

    public int getRadius() {
        return radius;
    }
}

  shapes/Rectangle.java: 矩形

package visitor.shapes;

import visitor.visitor.Visitor;

/**
 * @author GaoMing
 * @date 2021/7/26 - 17:26
 */
public class Rectangle implements Shape{
    private int id;
    private int x;
    private int y;
    private int width;
    private int height;

    public Rectangle(int id, int x, int y, int width, int height) {
        this.id = id;
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }

    @Override
    public String accept(Visitor visitor) {
        return visitor.visitRectangle(this);
    }

    @Override
    public void move(int x, int y) {
        // move shape
    }

    @Override
    public void draw() {
        // draw shape
    }

    public int getId() {
        return id;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public int getWidth() {
        return width;
    }

    public int getHeight() {
        return height;
    }
}

  shapes/CompoundShape.java: 組合形狀

package visitor.shapes;

import visitor.visitor.Visitor;

import java.util.ArrayList;
import java.util.List;

/**
 * @author GaoMing
 * @date 2021/7/26 - 17:27
 */
public class CompoundShape implements Shape{
    public int id;
    public List<Shape> children = new ArrayList<>();

    public CompoundShape(int id) {
        this.id = id;
    }

    @Override
    public void move(int x, int y) {
        // move shape
    }

    @Override
    public void draw() {
        // draw shape
    }

    public int getId() {
        return id;
    }

    @Override
    public String accept(Visitor visitor) {
        return visitor.visitCompoundGraphic(this);
    }

    public void add(Shape shape) {
        children.add(shape);
    }
}

  visitor/Visitor.java: 通用訪問者接口

package visitor.visitor;

import visitor.shapes.Circle;
import visitor.shapes.CompoundShape;
import visitor.shapes.Dot;
import visitor.shapes.Rectangle;

/**
 * @author GaoMing
 * @date 2021/7/26 - 17:24
 */
public interface Visitor {
    String visitDot(Dot dot);

    String visitCircle(Circle circle);

    String visitRectangle(Rectangle rectangle);

    String visitCompoundGraphic(CompoundShape cg);
}

  visitor/XMLExportVisitor.java: 具體訪問者,將全部形狀導出爲 XML 文件

package visitor.visitor;

import visitor.shapes.*;

/**
 * @author GaoMing
 * @date 2021/7/26 - 17:28
 */
public class XMLExportVisitor implements Visitor{
    public String export(Shape... args) {
        StringBuilder sb = new StringBuilder();
        sb.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "\n");
        for (Shape shape : args) {
            sb.append(shape.accept(this)).append("\n");
        }
        return sb.toString();
    }

    public String visitDot(Dot d) {
        return "<dot>" + "\n" +
                "    <id>" + d.getId() + "</id>" + "\n" +
                "    <x>" + d.getX() + "</x>" + "\n" +
                "    <y>" + d.getY() + "</y>" + "\n" +
                "</dot>";
    }

    public String visitCircle(Circle c) {
        return "<circle>" + "\n" +
                "    <id>" + c.getId() + "</id>" + "\n" +
                "    <x>" + c.getX() + "</x>" + "\n" +
                "    <y>" + c.getY() + "</y>" + "\n" +
                "    <radius>" + c.getRadius() + "</radius>" + "\n" +
                "</circle>";
    }

    public String visitRectangle(Rectangle r) {
        return "<rectangle>" + "\n" +
                "    <id>" + r.getId() + "</id>" + "\n" +
                "    <x>" + r.getX() + "</x>" + "\n" +
                "    <y>" + r.getY() + "</y>" + "\n" +
                "    <width>" + r.getWidth() + "</width>" + "\n" +
                "    <height>" + r.getHeight() + "</height>" + "\n" +
                "</rectangle>";
    }

    public String visitCompoundGraphic(CompoundShape cg) {
        return "<compound_graphic>" + "\n" +
                "   <id>" + cg.getId() + "</id>" + "\n" +
                _visitCompoundGraphic(cg) +
                "</compound_graphic>";
    }

    private String _visitCompoundGraphic(CompoundShape cg) {
        StringBuilder sb = new StringBuilder();
        for (Shape shape : cg.children) {
            String obj = shape.accept(this);
            // Proper indentation for sub-objects.
            obj = "    " + obj.replace("\n", "\n    ") + "\n";
            sb.append(obj);
        }
        return sb.toString();
    }

}

  Demo.java: 客戶端代碼

package visitor;

import visitor.shapes.*;
import visitor.visitor.XMLExportVisitor;

/**
 * @author GaoMing
 * @date 2021/7/26 - 17:23
 */
public class Demo {
    public static void main(String[] args) {
        Dot dot = new Dot(1, 10, 55);
        Circle circle = new Circle(2, 23, 15, 10);
        Rectangle rectangle = new Rectangle(3, 10, 17, 20, 30);

        CompoundShape compoundShape = new CompoundShape(4);
        compoundShape.add(dot);
        compoundShape.add(circle);
        compoundShape.add(rectangle);

        CompoundShape c = new CompoundShape(5);
        c.add(dot);
        compoundShape.add(c);

        export(circle, compoundShape);
    }

    private static void export(Shape... shapes) {
        XMLExportVisitor exportVisitor = new XMLExportVisitor();
        System.out.println(exportVisitor.export(shapes));
    }
}

  運行結果

<?xml version="1.0" encoding="utf-8"?>
<circle>
    <id>2</id>
    <x>23</x>
    <y>15</y>
    <radius>10</radius>
</circle>

<?xml version="1.0" encoding="utf-8"?>
<compound_graphic>
   <id>4</id>
    <dot>
        <id>1</id>
        <x>10</x>
        <y>55</y>
    </dot>
    <circle>
        <id>2</id>
        <x>23</x>
        <y>15</y>
        <radius>10</radius>
    </circle>
    <rectangle>
        <id>3</id>
        <x>10</x>
        <y>17</y>
        <width>20</width>
        <height>30</height>
    </rectangle>
    <compound_graphic>
       <id>5</id>
        <dot>
            <id>1</id>
            <x>10</x>
            <y>55</y>
        </dot>
    </compound_graphic>
</compound_graphic>    

  7. 與其餘模式的關係

  • 能夠將訪問者模式視爲命令模式的增強版本,其對象可對不一樣類的多種對象執行操做
  • 可使用訪問者對整個組合模式樹執行操做
  • 能夠同時使用訪問者和迭代器模式來遍歷複雜數據結構,並對其中的元素執行所需操做,即便這些元素所屬的類徹底不一樣。而迭代器可以訪問的全部元素都有一個共同的父類

  8. 已知應用  

  使用示例:訪問者不是經常使用的設計模式,由於它不只複雜,應用範圍也比較狹窄

  這裏是 Java 程序庫代碼中該模式的一些示例:  javax.lang.model.element.AnnotationValue 和 Annotation­Value­Visitor  javax.lang.model.element.Element 和 Element­Visitor  javax.lang.model.type.TypeMirror 和 Type­Visitor  java.nio.file.FileVisitor 和 Simple­File­Visitor  javax.faces.component.visit.VisitContext 和 Visit­Callback

相關文章
相關標籤/搜索