1. 程式人生 > >二十三種設計模式[23] - 訪問者模式(Visitor Pattern)

二十三種設計模式[23] - 訪問者模式(Visitor Pattern)

htm 訪問者模式 單一職責 script truct width string void sig

前言

訪問者模式,是一種將數據的結構與其操作分離的類行為型模式。它能夠幫助我們解決數據結構穩定但數據操作多變的問題,使我們可以很容易的增加或修改數據的操作。

在《設計模式 - 可復用的面向對象軟件》一書中將之描述為“ 表示一個作用於某對象結構中的各元素的操作。它使你可以在不改變各元素的類的前提下定義作用於這些元素的新操作 ”。

結構

技術分享圖片

  • Visitor(訪問者接口):定義了每種元素的訪問行為,一般情況下訪問行為的個數與元素種類的個數一致;
  • ConcretVisitor(具體訪問者):實現訪問者接口,實現每種元素具體的訪問行為;
  • Element(元素接口或抽象):用來定義以訪問者為入參的操作;
  • ConcreteElement(具體元素):用來實現以訪問者為入參的操作,該操作通常只是調用訪問者的訪問行為;
  • ObjectStructure(對象結構):元素的集合或組合,為其內部元素統一接收訪問者;

註:組合相關內容可參照組合模式(Composite Pattern)

示例

以操作系統中的目錄結構為例,看看如何使用訪問者模式來遍歷這個樹狀結構並獲得我們想要的信息。類結構如下:

技術分享圖片

在此結構的基礎上,模擬如下目錄結構:

技術分享圖片

實現如下:

  • 結構相關

public interface IFile
{
    void Add(IFile obj);
    void Accept(IFileVisitor visitor);
}

public class Folder : IFile
{
    public string Name { set; get; } = string.Empty;
    private List<IFile> _childList = new List<IFile>();

    public Folder(string name)
    {
        this.Name = name;
    }

    public void Add(IFile obj)
    {
        this._childList.Add(obj);
    }

    public void Accept(IFileVisitor visitor)
    {
        foreach (var item in this._childList)
        {
            item.Accept(visitor);
        }

        visitor.Visit(this);
    }
}

public class Png : IFile
{
    public string Name { set; get; } = string.Empty;

    public Png(string name)
    {
        this.Name = name;
    }

    public void Add(IFile obj)
    {
        throw new NotImplementedException("Sorry,I have not child");
    }

    public void Accept(IFileVisitor visitor)
    {
        visitor.Visit(this);
    }
}

public class Txt : IFile
{
    public string Name { set; get; } = string.Empty;

    public Txt(string name)
    {
        this.Name = name;
    }

    public void Add(IFile obj)
    {
        throw new NotImplementedException("Sorry,I have not Child");
    }

    public void Accept(IFileVisitor visitor)
    {
        visitor.Visit(this);
    }
}
  • 訪問者相關
public interface IFileVisitor
{
    void Visit(Folder folder);
    void Visit(Png png);
    void Visit(Txt txt);
}

/// <summary>
/// 統計文件夾個數的訪問者
/// </summary>
public class FolderSumVisitor : IFileVisitor
{
    public int Sum { private set; get; } = 0;

    public void Visit(Folder folder) { this.Sum++; }

    public void Visit(Png png) { }

    public void Visit(Txt txt) { }
}

/// <summary>
/// 統計png文件個數的訪問者
/// </summary>
public class PngSumVisitor : IFileVisitor
{
    public int Sum { private set; get; } = 0;

    public void Visit(Folder folder) { }

    public void Visit(Png png) { this.Sum++; }

    public void Visit(Txt txt) { }
}

/// <summary>
/// 統計txt文件名稱列表的訪問者
/// </summary>
public class TxtNameVisitor : IFileVisitor
{
    public List<string> NameList { private set; get; } = new List<string>();

    public void Visit(Folder folder) { }

    public void Visit(Png png) { }

    public void Visit(Txt txt) { this.NameList.Add(txt.Name); }
}
  • 調用
static void Main(string[] args)
{
    //創建目錄結構 Start
    IFile folderA = new Folder("FolderA");
    folderA.Add(new Txt("TxtA"));
    folderA.Add(new Png("PngA"));

    IFile folderB = new Folder("FolderB");
    folderB.Add(new Txt("TxtB"));
    folderB.Add(new Png("PngB"));

    IFile folderC = new Folder("FolderC");
    folderC.Add(new Txt("TxtC"));
    folderC.Add(new Png("PngC"));

    folderB.Add(folderC);
    IFile folder = new Folder("Folder");
    folder.Add(folderA);
    folder.Add(folderB);
    //創建目錄結構 End

    FolderSumVisitor folderSumVisitor = new FolderSumVisitor();
    folder.Accept(folderSumVisitor);
    Console.WriteLine($"共有文件夾{folderSumVisitor.Sum}個");

    PngSumVisitor pngSumVisitor = new PngSumVisitor();
    folder.Accept(pngSumVisitor);
    Console.WriteLine($"共有png文件{pngSumVisitor.Sum}個");

    TxtNameVisitor txtNameVisitor = new TxtNameVisitor();
    folder.Accept(txtNameVisitor);
    Console.WriteLine($"{Environment.NewLine}所有的txt文件名如下:");
    txtNameVisitor.NameList.ForEach(t => Console.WriteLine(t));

    Console.ReadKey();
}

技術分享圖片

示例中使用了組合模式(Composite Pattern)來描述這個目錄結構。在該結構中,根節點Folder類中的Accept函數將接收到的訪問者依次傳入其子節點的Accept函數後調用該訪問者的Visit函數。而子節點Txt和Png類中的Accept函數則只是調用其接收的訪問者的Visit函數。訪問者利用重載,通過傳入的節點類型來判斷具體調用的函數。

由於訪問者與結構之間相對獨立,所以我們在修改訪問者的訪問行為時不必對結構做出改動。而當我們需要增加對於該結構的遍歷邏輯時,只需要增加對應的訪問者即可。改動如下:

技術分享圖片

看得出來,在訪問者模式中增加一個訪問者是非常輕松的,並不需要修改其它的文件,復合開閉原則。如果需要增加一個新的元素類型呢?如下。

技術分享圖片

這種情況下,在增加元素類型的同時需要對所有訪問者類做出對應改動!這也是為什麽說訪問者模式能夠幫助我們解決的是數據結構穩定但數據操作多變的問題。

總結

訪問者模式將數據的結構與其操作分離,使得操作可以獨立變化而不會影響到數據的結構,同時它們的職責也更加明確,復合單一職責原則。這種方式也使得我們在增加一個訪問者(操作)時變得異常簡單,但在增加元素(結構)時卻變得異常恐怖。分離的好處在於相對獨立,但也正是因為相對獨立的關系,元素又不得不向訪問者暴露一些內部的狀態和結構。

最後,在使用訪問者模式之前一定要確認數據的結構是否足夠穩定!!!


以上,就是我對訪問者模式的理解,希望對你有所幫助。

示例源碼:https://gitee.com/wxingChen/DesignPatternsPractice

系列匯總:https://www.cnblogs.com/wxingchen/p/10031592.html

本文著作權歸本人所有,如需轉載請標明本文鏈接(https://www.cnblogs.com/wxingchen/p/10355609.html)

二十三種設計模式[23] - 訪問者模式(Visitor Pattern)