.Net行為型設計模式之訪問者模式(Visitor)

一、動機(Motivate)

在軟件構建過程中,由於需求的改變,某些類層次結構中常常需要增加新的行為(方法),如果直接在基類中做這樣的更改,將會給子類帶來很繁重的變更負擔,甚至破壞原有設計。如何在不更改類層次結構的前提下,在運行時根據需要透明地為類層次結構上的各個類動態添加新的操作,從而避免上述問題?

二、意圖(Intent)

表示一個作用於某對象結構中的各個元素的操作。它可以在不改變各元素的類的前提下定義作用於這些元素的新的操作。                                 ——《設計模式》GoF

三、結構圖(Structure)

四、模式的組成

可以看出,在訪問者模式的結構圖有以下角色:
(1)、抽象訪問者角色(Vistor): 聲明一個包括多個訪問操作,多個操作針對多個具體節點角色(可以說有多少個具體節點角色就有多少訪問操作),使得所有具體訪問者必須實現的接口。
(2)、具體訪問者角色(ConcreteVistor):實現抽象訪問者角色中所有聲明的接口,也可以說是實現對每個具體節點角色的新的操作。
(3)、抽象節點角色(Element):聲明一個接受操作,接受一個訪問者對象作為參數,如果有其他參數,可以在這個“接受操作”裡在定義相關的參數。
(4)、具體節點角色(ConcreteElement):實現抽象元素所規定的接受操作。
(5)、結構對象角色(ObjectStructure):節點的容器,可以包含多個不同類或接口的容器。

五、訪問者模式的代碼實現

訪問者這個模式在我們現實的編碼生活中使用的並不是很多,我就直接貼代碼,讓大傢看代碼的結構吧。今天給大傢兩個代碼實例,自己慢慢體會訪問者吧。實現代碼如下:

static void Main(string[] args)
{
    //如果想執行新增加的操作
    ShapeVisitor visitor = new CustomVisitor();
    AppStructure app = new AppStructure(visitor);

    Shape shape = new Rectangle();
    shape.Draw();//執行自己的操作
    app.Process(shape);//執行新的操作


    shape = new Circle();
    shape.Draw();//執行自己的操作
    app.Process(shape);//執行新的操作


    shape = new Line();
    shape.Draw();//執行自己的操作
    app.Process(shape);//執行新的操作

}

//抽象圖形定義---相當於“抽象節點角色”Element
public abstract class Shape
{
    //畫圖形
    public abstract void Draw();
    //外界註入具體訪問者
    public abstract void Accept(ShapeVisitor visitor);
}

//抽象訪問者 Visitor
public abstract class ShapeVisitor
{
    public abstract void Visit(Rectangle shape);

    public abstract void Visit(Circle shape);

    public abstract void Visit(Line shape);

    //這裡有一點要說:Visit方法的參數可以寫成Shape嗎?就是這樣 Visit(Shape shape),當然可以,但是ShapeVisitor子類Visit方法就需要判斷當前的Shape是什麼類型,是Rectangle類型,是Circle類型,或者是Line類型。
}

//具體訪問者 ConcreteVisitor
public sealed class CustomVisitor : ShapeVisitor
{
    //針對Rectangle對象
    public override void Visit(Rectangle shape)
    {
        Console.WriteLine("針對Rectangle新的操作!");
    }
    //針對Circle對象
    public override void Visit(Circle shape)
    {
        Console.WriteLine("針對Circle新的操作!");
    }
    //針對Line對象
    public override void Visit(Line shape)
    {
        Console.WriteLine("針對Line新的操作!");
    }
}

//矩形----相當於“具體節點角色” ConcreteElement
public sealed class Rectangle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("矩形我已經畫好!");
    }

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

//圓形---相當於“具體節點角色”ConcreteElement
public sealed class Circle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("圓形我已經畫好!");
    }

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

//直線---相當於“具體節點角色” ConcreteElement
public sealed class Line : Shape
{
    public override void Draw()
    {
        Console.WriteLine("直線我已經畫好!");
    }

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

//結構對象角色
internal class AppStructure
{
    private ShapeVisitor _visitor;

    public AppStructure(ShapeVisitor visitor)
    {
        this._visitor = visitor;
    }

    public void Process(Shape shape)
    {
        shape.Accept(_visitor);
    }
}

這是訪問者模式第二種代碼實例:

static void Main(string[] args)
{
    StoragePlatform platform = new StoragePlatform();
    platform.Attach(new Television());
    platform.Attach(new Computer());

    SizeVisitor sizeVisitor = new SizeVisitor();
    StateVisitor stateVisitor = new StateVisitor();

    platform.Operate(sizeVisitor);
    platform.Operate(stateVisitor);
}
//抽象訪問者角色 Visitor
public abstract class Visitor
{
    public abstract void PutTelevision(Television tv);

    public abstract void PutComputer(Computer comp);
}

//具體訪問者角色 ConcreteVisitor
public sealed class SizeVisitor : Visitor
{
    public override void PutTelevision(Television tv)
    {
        Console.WriteLine("按商品大小{0}排放", tv.Size);
    }

    public override void PutComputer(Computer comp)
    {
        Console.WriteLine("按商品大小{0}排放", comp.Size);
    }
}

//具體訪問者角色 ConcreteVisitor
public sealed class StateVisitor : Visitor
{
    public override void PutTelevision(Television tv)
    {
        Console.WriteLine("按商品新舊值{0}排放", tv.State);
    }

    public override void PutComputer(Computer comp)
    {
        Console.WriteLine("按商品新舊值{0}排放", comp.State);
    }
}

//抽象節點角色 Element
public abstract class Goods
{
    public abstract void Operate(Visitor visitor);

    private int nSize;
    public int Size
    {
        get { return nSize; }
        set { nSize = value; }
    }

    private int nState;
    public int State
    {
        get { return nState; }
        set { nState = value; }
    }
}

//具體節點角色 ConcreteElement
public sealed class Television : Goods
{
    public override void Operate(Visitor visitor)
    {
        visitor.PutTelevision(this);
    }
}

//具體節點角色 ConcreteElement
public sealed class Computer : Goods
{
    public override void Operate(Visitor visitor)
    {
        visitor.PutComputer(this);
    }
}

//結構對象角色
public sealed class StoragePlatform
{
    private IList<Goods> list = new List<Goods>();

    public void Attach(Goods element)
    {
        list.Add(element);
    }

    public void Detach(Goods element)
    {
        list.Remove(element);
    }

    public void Operate(Visitor visitor)
    {
        foreach (Goods g in list)
        {
            g.Operate(visitor);
        }
    }
}

六、訪問者模式的實現要點:

Visitor模式通過所謂雙重分發(double dispatch)來實現在不更改Element類層次結構的前提下,在運行時透明地為類層次結構上的各個類動態添加新的操作。所謂雙重分發即Visitor模式中間包括瞭兩個多態分發(註意其中的多態機制):第一個為accept方法的多態辨析;第二個為visit方法的多態辨析。
設計模式其實是一種堵漏洞的方式,但是沒有一種設計模式能夠堵完所有的漏洞,即使是組合各種設計模式也是一樣。每個設計模式都有漏洞,都有它們解決不瞭的情況或者變化。每一種設計模式都假定瞭某種變化,也假定瞭某種不變化。Visitor模式假定的就是操作變化,而Element類層次結構穩定。

(1)、訪問者模式的主要優點有:

1】、訪問者模式使得添加新的操作變得容易。如果一些操作依賴於一個復雜的結構對象的話,那麼一般而言,添加新的操作會變得很復雜。而使用訪問者模式,增加新的操作就意味著添加一個新的訪問者類。因此,使得添加新的操作變得容易。
2】、訪問者模式使得有關的行為操作集中到一個訪問者對象中,而不是分散到一個個的元素類中。這點類似與”中介者模式”。
3】、訪問者模式可以訪問屬於不同的等級結構的成員對象,而迭代隻能訪問屬於同一個等級結構的成員對象。

(2)、訪問者模式的主要缺點有:

增加新的元素類變得困難。每增加一個新的元素意味著要在抽象訪問者角色中增加一個新的抽象操作,並在每一個具體訪問者類中添加相應的具體操作。具體來說,Visitor模式的最大缺點在於擴展類層次結構(增添新的Element子類),會導致Visitor類的改變。因此Visitor模式適用於“Element類層次結構穩定,而其中的操作卻經常面臨頻繁改動”。

(3)、在下面的情況下可以考慮使用訪問者模式:

1】、如果系統有比較穩定的數據結構,而又有易於變化的算法時,此時可以考慮使用訪問者模式。因為訪問者模式使得算法操作的添加比較容易。
2】、如果一組類中,存在著相似的操作,為瞭避免出現大量重復的代碼,可以考慮把重復的操作封裝到訪問者中。(當然也可以考慮使用抽象類瞭)
3】、如果一個對象存在著一些與本身對象不相幹,或關系比較弱的操作時,為瞭避免操作污染這個對象,則可以考慮把這些操作封裝到訪問者對象中。

七、.NET 訪問者模式的實現

在現在的Net框架裡面,如果要想給現有的類增加新的方法,有瞭新的方式,那就是“擴展方法”,使用起來和實例方法是一樣一樣的,而且在Net框架裡面,微軟自己也寫瞭很多的擴展方法給我們使用。我目前還沒有學習到Net的框架類庫裡面有“訪問者模式”實現,看來自己還需努力,革命尚未成功啊。

到此這篇關於.Net行為型設計模式之訪問者模式(Visitor)的文章就介紹到這瞭。希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。

推薦閱讀: