本筆記摘抄自:https://www.cnblogs.com/PatrickLiu/p/8135083.html,記錄一下學(xué)習(xí)過(guò)程以備后續(xù)查用。
一、引言
今天我們要講行為型設(shè)計(jì)模式的第九個(gè)模式--訪問(wèn)者模式。如果按老規(guī)矩,先從名稱上來(lái)看這個(gè)模式,我根本不能獲得任何對(duì)理解該模式有用的信息,
而且這個(gè)模式在我們的編碼生活中使用的并不是很多。該模式的意圖定義很抽象,第一次看了這個(gè)定義其實(shí)和沒(méi)看沒(méi)有什么區(qū)別,一頭霧水。為了讓大家
更好的理解該模式的初衷,我們舉個(gè)例子來(lái)說(shuō)明模式。比如:當(dāng)客戶提出一個(gè)新的軟件需求的時(shí)候,大家經(jīng)過(guò)多個(gè)日以繼夜的努力,終于通過(guò)一個(gè)比較不
錯(cuò)的軟件設(shè)計(jì)解決了客戶的需求,而且這個(gè)設(shè)計(jì)有相對(duì)完美的類層次結(jié)構(gòu)并且符合OO的設(shè)計(jì)原則,大家為此感到開(kāi)心,頗有成就感。又過(guò)了一段時(shí)間,
客戶突然又有一個(gè)新的需求,需要在現(xiàn)有的類層次結(jié)構(gòu)里面增加一個(gè)新的操作(其實(shí)就是一個(gè)方法),怎么辦?還好,在面向OO設(shè)計(jì)模式中有一個(gè)模式
就是為了解決這個(gè)問(wèn)題的,那就是訪問(wèn)者模式,下面讓我們好好地了解一下該模式。
二、訪問(wèn)者模式介紹
訪問(wèn)者模式:英文名稱--Visitor Pattern;分類--行為型。
2.1、動(dòng)機(jī)(Motivate)
在軟件構(gòu)建過(guò)程中,由于需求的改變,某些類層次結(jié)構(gòu)中常常需要增加新的行為(方法),如果直接在基類中做這樣的更改,將會(huì)給子類帶來(lái)很繁重的
變更負(fù)擔(dān),甚至破壞原有設(shè)計(jì)。如何在不更改類層次結(jié)構(gòu)的前提下,在運(yùn)行時(shí)根據(jù)需要透明地為類層次結(jié)構(gòu)上的各個(gè)類動(dòng)態(tài)添加新的操作?
2.2、意圖(Intent)
表示一個(gè)作用于某對(duì)象結(jié)構(gòu)中的各個(gè)元素的操作。它可以在不改變各元素的類的前提下定義作用于這些元素的新的操作?!对O(shè)計(jì)模式》GoF
2.3、結(jié)構(gòu)圖(Structure)
2.4、模式的組成
可以看出,在訪問(wèn)者模式的結(jié)構(gòu)圖有以下角色:
1)抽象訪問(wèn)者角色(Visitor): 聲明一個(gè)包括多個(gè)訪問(wèn)操作,多個(gè)操作針對(duì)多個(gè)具體節(jié)點(diǎn)角色(可以說(shuō)有多少個(gè)具體節(jié)點(diǎn)角色就有多少訪問(wèn)操作),使
得所有具體訪問(wèn)者必須實(shí)現(xiàn)的接口。
2)具體訪問(wèn)者角色(ConcreteVisitor):實(shí)現(xiàn)抽象訪問(wèn)者角色中所有聲明的接口,也可以說(shuō)是實(shí)現(xiàn)對(duì)每個(gè)具體節(jié)點(diǎn)角色的新的操作。
3)抽象節(jié)點(diǎn)角色(Element):聲明一個(gè)接受操作,接受一個(gè)訪問(wèn)者對(duì)象作為參數(shù),如果有其它參數(shù),可以在這個(gè)“接受操作”里在定義相關(guān)的參數(shù)。
4)具體節(jié)點(diǎn)角色(ConcreteElement):實(shí)現(xiàn)抽象元素所規(guī)定的接受操作。
5)結(jié)構(gòu)對(duì)象角色(ObjectStructure):節(jié)點(diǎn)的容器,可以包含多個(gè)不同類或接口的容器。
2.5、訪問(wèn)者模式的具體實(shí)現(xiàn)
訪問(wèn)者模式在我們現(xiàn)實(shí)的編碼生活中使用的并不是很多,我就直接貼代碼,讓大家看看代碼的結(jié)構(gòu)吧。今天給大家兩個(gè)代碼實(shí)例,實(shí)現(xiàn)代碼如下:
class Program { /// <summary> /// 抽象圖形定義--相當(dāng)于“抽象節(jié)點(diǎn)角色” /// </summary> public abstract class Shape { //畫(huà)圖形 public abstract void Draw(); //外界注入具體訪問(wèn)者 public abstract void Accept(ShapeVisitor visitor); } /// <summary> /// 矩形--相當(dāng)于“具體節(jié)點(diǎn)角色” /// </summary> public sealed class Rectangle : Shape { public override void Draw() { Console.WriteLine("矩形我已經(jīng)畫(huà)好了。"); } public override void Accept(ShapeVisitor visitor) { visitor.Visit(this); } } /// <summary> /// 圓形--相當(dāng)于“具體節(jié)點(diǎn)角色” /// </summary> public sealed class Circle : Shape { public override void Draw() { Console.WriteLine("圓形我已經(jīng)畫(huà)好了。"); } public override void Accept(ShapeVisitor visitor) { visitor.Visit(this); } } /// <summary> /// 直線--相當(dāng)于“具體節(jié)點(diǎn)角色” /// </summary> public sealed class Line : Shape { public override void Draw() { Console.WriteLine("直線我已經(jīng)畫(huà)好了。"); } public override void Accept(ShapeVisitor visitor) { visitor.Visit(this); } } /// <summary> /// 抽象訪問(wèn)者 /// </summary> public abstract class ShapeVisitor { public abstract void Visit(Rectangle shape); public abstract void Visit(Circle shape); public abstract void Visit(Line shape); //這里有一點(diǎn)要說(shuō):Visit方法的參數(shù)可以寫成Shape嗎?就是這樣Visit(Shape shape)。 //答案是可以,但是ShapeVisitor子類的Visit方法就需要判斷當(dāng)前的Shape是什么類型?是Rectangle類型?還是Circle類型?或者是Line類型? } /// <summary> /// 具體訪問(wèn)者 /// </summary> public sealed class CustomVisitor : ShapeVisitor { //針對(duì)Rectangle對(duì)象 public override void Visit(Rectangle shape) { Console.WriteLine("針對(duì)Rectangle新的操作。"); } //針對(duì)Circle對(duì)象 public override void Visit(Circle shape) { Console.WriteLine("針對(duì)Circle新的操作。"); } //針對(duì)Line對(duì)象 public override void Visit(Line shape) { Console.WriteLine("針對(duì)Line新的操作。"); } } /// <summary> /// 結(jié)構(gòu)對(duì)象角色 /// </summary> internal class AppStructure { private readonly ShapeVisitor _visitor; public AppStructure(ShapeVisitor visitor) { _visitor = visitor; } public void Process(Shape shape) { shape.Accept(_visitor); } } static void Main(string[] args) { #region 訪問(wèn)者模式(第一個(gè)實(shí)例) ShapeVisitor visitor = new CustomVisitor(); AppStructure app = new AppStructure(visitor); Shape shape = new Rectangle(); shape.Draw(); //執(zhí)行自己的操作 app.Process(shape); //執(zhí)行新的操作 shape = new Circle(); shape.Draw(); //執(zhí)行自己的操作 app.Process(shape); //執(zhí)行新的操作 shape = new Line(); shape.Draw(); //執(zhí)行自己的操作 app.Process(shape); //執(zhí)行新的操作 Console.ReadLine(); #endregion } }
運(yùn)行結(jié)果如下:
這是訪問(wèn)者模式第二個(gè)代碼實(shí)例:
class Program { /// <summary> /// 抽象訪問(wèn)者角色 /// </summary> public abstract class Visitor { public abstract void PutTelevision(Television tv); public abstract void PutComputer(Computer comp); } /// <summary> /// 具體訪問(wèn)者角色 /// </summary> 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); } } /// <summary> /// 具體訪問(wèn)者角色 /// </summary> 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); } } /// <summary> /// 抽象節(jié)點(diǎn)角色 /// </summary> public abstract class Goods { public abstract void Operate(Visitor visitor); public int Size { get; set; } public int State { get; set; } } /// <summary> /// 具體節(jié)點(diǎn)角色 /// </summary> public sealed class Television : Goods { public override void Operate(Visitor visitor) { visitor.PutTelevision(this); } } /// <summary> /// 具體節(jié)點(diǎn)角色 /// </summary> public sealed class Computer : Goods { public override void Operate(Visitor visitor) { visitor.PutComputer(this); } } /// <summary> /// 結(jié)構(gòu)對(duì)象角色 /// </summary> 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); } } } static void Main(string[] args) { #region 訪問(wèn)者模式(第二個(gè)實(shí)例) 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); Console.Read(); #endregion } }
運(yùn)行結(jié)果如下:
三、訪問(wèn)者模式的實(shí)現(xiàn)要點(diǎn)
Visitor模式通過(guò)所謂雙重分發(fā)(double dispatch)來(lái)實(shí)現(xiàn)在不更改Element類層次結(jié)構(gòu)的前提下,在運(yùn)行時(shí)透明地為類層次結(jié)構(gòu)上的各個(gè)類動(dòng)態(tài)添加新
的操作。所謂雙重分發(fā)即Visitor模式中間包括了兩個(gè)多態(tài)分發(fā)(注意其中的多態(tài)機(jī)制):第一個(gè)為accept方法的多態(tài)辨析;第二個(gè)為visit方法的多態(tài)辨析。
設(shè)計(jì)模式其實(shí)是一種堵漏洞的方式,但是沒(méi)有一種設(shè)計(jì)模式能夠堵完所有的漏洞,即使是組合各種設(shè)計(jì)模式也是一樣。每個(gè)設(shè)計(jì)模式都有漏洞,都有
它們解決不了的情況或者變化。每一種設(shè)計(jì)模式都假定了某種變化,也假定了某種不變化。Visitor模式假定的就是操作變化,而Element類層次結(jié)構(gòu)穩(wěn)定。
3.1、訪問(wèn)者模式的主要優(yōu)點(diǎn)
1)訪問(wèn)者模式使得添加新的操作變得容易。如果一些操作依賴于一個(gè)復(fù)雜的結(jié)構(gòu)對(duì)象的話,那么一般而言,添加新的操作會(huì)變得很復(fù)雜。而使用訪問(wèn)
者模式,增加新的操作就意味著添加一個(gè)新的訪問(wèn)者類。因此,使得添加新的操作變得容易。
2)訪問(wèn)者模式使得有關(guān)的行為操作集中到一個(gè)訪問(wèn)者對(duì)象中,而不是分散到一個(gè)個(gè)的元素類中。這點(diǎn)類似與”中介者模式”。
3)訪問(wèn)者模式可以訪問(wèn)屬于不同的等級(jí)結(jié)構(gòu)的成員對(duì)象,而迭代只能訪問(wèn)屬于同一個(gè)等級(jí)結(jié)構(gòu)的成員對(duì)象。
3.2、訪問(wèn)者模式的主要缺點(diǎn)
1)增加新的元素類變得困難。每增加一個(gè)新的元素意味著要在抽象訪問(wèn)者角色中增加一個(gè)新的抽象操作,并在每一個(gè)具體訪問(wèn)者類中添加相應(yīng)的具體
操作。具體來(lái)說(shuō),Visitor模式的最大缺點(diǎn)在于擴(kuò)展類層次結(jié)構(gòu)(增添新的Element子類),會(huì)導(dǎo)致Visitor類的改變。因此Visitor模式適用于“Element類層次
結(jié)構(gòu)穩(wěn)定,而其中的操作卻經(jīng)常面臨頻繁改動(dòng)”。
3.3、在下面的情況下可以考慮使用訪問(wèn)者模式
1)如果系統(tǒng)有比較穩(wěn)定的數(shù)據(jù)結(jié)構(gòu),而又有易于變化的算法時(shí),此時(shí)可以考慮使用訪問(wèn)者模式,因?yàn)樵L問(wèn)者模式使得算法操作的添加比較容易。
2)如果一組類中,存在著相似的操作,為了避免出現(xiàn)大量重復(fù)的代碼,可以考慮把重復(fù)的操作封裝到訪問(wèn)者中(當(dāng)然也可以考慮使用抽象類了)。
3)如果一個(gè)對(duì)象存在著一些與本身對(duì)象不相干或關(guān)系比較弱的操作時(shí),為了避免操作污染這個(gè)對(duì)象,則可以考慮把這些操作封裝到訪問(wèn)者對(duì)象中。
四、.NET中訪問(wèn)者模式的實(shí)現(xiàn)
在現(xiàn)在的.Net框架里面,如果要想給現(xiàn)有的類增加新的方法,有了新的方式,那就是“擴(kuò)展方法”,使用起來(lái)和實(shí)例方法是一樣一樣的,而且在.Net
框架里面,微軟自己也寫了很多的擴(kuò)展方法給我們使用。我目前還沒(méi)有學(xué)習(xí)到.Net的框架類庫(kù)里面有“訪問(wèn)者模式”實(shí)現(xiàn),看來(lái)自己還需努力,革命尚
未成功啊。
五、總結(jié)
訪問(wèn)者模式寫完了,這個(gè)模式剛開(kāi)始理解起來(lái)還是挺麻煩的,但是,如果我們多看幾個(gè)實(shí)例代碼,完全掌握也不是問(wèn)題。隨著C#語(yǔ)言的發(fā)展,設(shè)
計(jì)模式里面的很多東西,我們可以通過(guò)C#語(yǔ)言的一些特性做更好的替代。我們寫設(shè)計(jì)模式剛開(kāi)始要慢慢來(lái),一步一步照貓畫(huà)虎地來(lái)寫代碼,等我們
熟練掌握了模式的核心思想,我們就要寫符合C#風(fēng)格和特性的模式代碼了,或者說(shuō)我們要用C#來(lái)寫設(shè)計(jì)模式了,寫出來(lái)的代碼也會(huì)更棒。
聯(lián)系客服