最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一起詳細(xì)聊聊C#中的Visitor模式

 更新時間:2022年05月20日 15:52:59   作者:老胡寫代碼  
Visitor模式表示一個作用于某對象結(jié)構(gòu)中的各元素的操作,下面這篇文章主要給大家介紹了關(guān)于C#中Visitor模式的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下

寫在前面

Visitor模式在日常工作中出場比較少,如果統(tǒng)計(jì)大家不熟悉的模式,那么它榜上有名的可能性非常大。使用頻率少,再加上很多文章提到Visitor模式都著重于它克服語言單分派的特點(diǎn)上面,而對何時應(yīng)該使用這個模式及這個模式是怎么一點(diǎn)點(diǎn)演講出來的提之甚少,造成很多人對這個模式有種霧里看花的感覺,今天跟著老胡,我們一起來一點(diǎn)點(diǎn)揭開它的面紗吧。

模式演進(jìn)

舉個例子

現(xiàn)在假設(shè)我們有一個簡單的需求,需要統(tǒng)計(jì)出一篇文檔中的字?jǐn)?shù)、詞數(shù)和圖片數(shù)量。其中字?jǐn)?shù)和詞數(shù)存在于段落中,圖片數(shù)量單獨(dú)統(tǒng)計(jì)。于是乎,我們可以很快的寫出第一版代碼

使用了基本抽象的版本

    abstract class DocumentElement
    {
        public abstract void UpdateStatus(DocumentStatus status);
    }

    public class DocumentStatus
    {
        public int CharNum { get; set; }
        public int WordNum { get; set; }
        public int ImageNum { get; set; }
        public void ShowStatus()
        {
            Console.WriteLine("I have {0} char, {1} word and {2} image", CharNum, WordNum, ImageNum);
        }
    }

    class ImageElement : DocumentElement
    {
        public override void UpdateStatus(DocumentStatus status)
        {
            status.ImageNum++;
        }
    }

    class ParagraphElement : DocumentElement
    {
        public int CharNum { get; set; }
        public int WordNum { get; set; }

        public ParagraphElement(int charNum, int wordNum)
        {
            CharNum = charNum;
            WordNum = wordNum;
        }

        public override void UpdateStatus(DocumentStatus status)
        {
            status.CharNum += CharNum;
            status.WordNum += WordNum;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            DocumentStatus docStatus = new DocumentStatus();
            List<DocumentElement> list = new List<DocumentElement>();
            DocumentElement e1 = new ImageElement();
            DocumentElement e2 = new ParagraphElement(10, 20);
            list.Add(e1);
            list.Add(e2);
            list.ForEach(e => e.UpdateStatus(docStatus));
            docStatus.ShowStatus();
        }
    }

運(yùn)行結(jié)果如下,非常簡單

但是細(xì)看這版代碼,會發(fā)現(xiàn)有以下問題:

  • 所有的DocumentElement派生類必須訪問DocumentStatus,根據(jù)迪米特法則,這不是個好現(xiàn)象,如果在未來對DocumentStatus有修改,這些派生類被波及的可能性極大
  • 統(tǒng)計(jì)代碼散落在不同的派生類里面,維護(hù)不方便

有鑒于此,我們推出了第二版代碼

使用了Tpye-Switch的版本

這一版代碼中,我們摒棄了之前在具體的DocumentElement派生類中進(jìn)行統(tǒng)計(jì)的做法,直接在統(tǒng)計(jì)類中統(tǒng)一處理

    public abstract class DocumentElement
    {
        //nothing to do now
    }

    public class DocumentStatus
    {
        public int CharNum { get; set; }
        public int WordNum { get; set; }
        public int ImageNum { get; set; }
        public void ShowStatus()
        {
            Console.WriteLine("I have {0} char, {1} word and {2} image", CharNum, WordNum, ImageNum);
        }

        public void Update(DocumentElement documentElement)
        {
            switch(documentElement)
            {
                case ImageElement imageElement:
                    ImageNum++;
                    break;

                case ParagraphElement paragraphElement:
                    WordNum += paragraphElement.WordNum;
                    CharNum += paragraphElement.CharNum;
                    break;
            }
        }
    }

    public class ImageElement : DocumentElement
    {

    }

    public class ParagraphElement : DocumentElement
    {
        public int CharNum { get; set; }
        public int WordNum { get; set; }

        public ParagraphElement(int charNum, int wordNum)
        {
            CharNum = charNum;
            WordNum = wordNum;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            DocumentStatus docStatus = new DocumentStatus();
            List<DocumentElement> list = new List<DocumentElement>();
            DocumentElement e1 = new ImageElement();
            DocumentElement e2 = new ParagraphElement(10, 20);
            list.Add(e1);
            list.Add(e2);
            docStatus.ShowStatus();
        }
    }

測試結(jié)果和第一個版本的代碼一樣,這一版代碼克服了第一個版本中,統(tǒng)計(jì)代碼散落,具體類依賴統(tǒng)計(jì)類的問題,轉(zhuǎn)而我們在統(tǒng)計(jì)類中集中處理了統(tǒng)計(jì)任務(wù)。但同時它引入了type-switch, 這也是一個不好的信號,具體表現(xiàn)在:

  • 代碼冗長且難以維護(hù)
  • 如果派生層次加多,需要很小心的選擇case順序以防出現(xiàn)繼承層次較低的類出現(xiàn)在繼承層次更遠(yuǎn)的類前面,從而造成后面的case永遠(yuǎn)無法被訪問的情況,這造成了額外的精力成本

嘗試使用重載的版本

有鑒于上面type-switch版本的問題,作為敏銳的程序員,可能馬上有人就會提出重載方案:“如果我們針對每個具體的DocumentElement寫出相應(yīng)的Update方法,不就可以了嗎?”就像下面這樣

    public class DocumentStatus
    {
        //省略相同代碼
        public void Update(ImageElement imageElement)
        {
           ImageNum++;
        }

        public void Update(ParagraphElement paragraphElement)
        {
           WordNum += paragraphElement.WordNum;
           CharNum += paragraphElement.CharNum;
        }
    }

    //省略相同代碼
    class Program
    {
        static void Main(string[] args)
        {
            DocumentStatus docStatus = new DocumentStatus();
            List<DocumentElement> list = new List<DocumentElement>();
            list.Add(new ImageElement());
            list.Add(new ParagraphElement(10, 20));
            list.ForEach(e => docStatus.Update(e));
            docStatus.ShowStatus();
        }
    }

看起來很好,不過可惜,這段代碼編譯失敗,編譯器會抱怨說,不能將DocumentElement轉(zhuǎn)為它的子類,這是為什么呢?講到這里,就不能不提一下編程語言中的單分派和雙分派

單分派與雙分派

大家都知道,多態(tài)是OOP的三個基本特征之一,即形如以下的代碼

    public class Father
    {
	public virtual void DoSomething(string str){}
    }

    public class Son : Father
    {
	public override void DoSomething(string str){}
    }

    Father son = new Son();
    son.DoSomething();

son 雖然被聲明為Father類型,但在運(yùn)行時會被動態(tài)綁定到其實(shí)際類型Son并調(diào)用到正確的被重寫后的函數(shù),這是多態(tài),通過調(diào)用函數(shù)的對象執(zhí)行動態(tài)綁定。在主流語言,比如C#, C++ 和 JAVA中,編譯器在編譯類函數(shù)的時候會進(jìn)行擴(kuò)充,把this指針隱含的傳遞到方法里面,上面的方法會擴(kuò)充為

    void DoSomething(this, string);
    void DoSomething(this, string);

在多態(tài)中實(shí)現(xiàn)的this指針動態(tài)綁定,其實(shí)是針對函數(shù)的第一個參數(shù)進(jìn)行運(yùn)行時動態(tài)綁定,這個也是單分派的定義。

至于雙分派,顧名思義,就是可以針對兩個參數(shù)進(jìn)行運(yùn)行時綁定的分派方法,不過可惜,C#等都不支持,所以大家現(xiàn)在應(yīng)該能理解為什么上面的代碼不能通過編譯了吧,上面的代碼通過編譯器的擴(kuò)充,變成了

    public void Update(DocumentStatus status, ImageElement imageElement)
    public void Update(DocumentStatus status, ParagraphElement imageElement)

因?yàn)镃#不支持雙分派,第二參數(shù)無法動態(tài)解析,所以就算實(shí)際類型是ImageElement,但是聲明類型是其基類DocumentElement,也會被編譯器拒絕。

所以,為了在本不支持雙分派的C#中實(shí)現(xiàn)雙分派,我們需要添加一個跳板函數(shù),通過這個函數(shù),我們讓第二參數(shù)充當(dāng)被調(diào)用對象,實(shí)現(xiàn)動態(tài)綁定,從而找到正確的重載函數(shù),我們需要引出今天的主角,Visitor模式。

Visitor模式

Visitor is a behavioral design pattern that lets you separate algorithms from the objects on which they operate.

翻譯的更直白一點(diǎn),Visitor模式允許針對不同的具體類型定制不同的訪問方法,而這個訪問者本身,也可以是不同的類型,看一下UML

在Visitor模式中,我們需要把訪問者抽象出來,以方便之后定制更多的不同類型的訪問者

抽象出DocumentElementVisitor,含有兩個版本的Visit方法,在其子類中具體定制針對不同類型的訪問方法

    public abstract class DocumentElementVisitor
    {
        public abstract void Visit(ImageElement imageElement);
        public abstract void Visit(ParagraphElement imageElement);
    }

    public class DocumentStatus : DocumentElementVisitor
    {
        public int CharNum { get; set; }
        public int WordNum { get; set; }
        public int ImageNum { get; set; }
        public void ShowStatus()
        {
            Console.WriteLine("I have {0} char, {1} word and {2} image", CharNum, WordNum, ImageNum);
        }

        public void Update(DocumentElement documentElement)
        {
            documentElement.Accept(this);
        }

        public override void Visit(ImageElement imageElement)
        {
            ImageNum++;
        }

        public override void Visit(ParagraphElement paragraphElement)
        {
            WordNum += paragraphElement.WordNum;
            CharNum += paragraphElement.CharNum;
        }
    }

在被訪問類的基類中添加一個Accept方法,這個方法用來實(shí)現(xiàn)雙分派,這個方法就是我們前文提到的跳板函數(shù),它的作用就是讓第二參數(shù)充當(dāng)被調(diào)用對象,第二次利用多態(tài)(第一次多態(tài)發(fā)生在調(diào)用Accept方法的時候)

    public abstract class DocumentElement
    {
        public abstract void Accept(DocumentElementVisitor visitor);
    }
    
    public class ImageElement : DocumentElement
    {
        public override void Accept(DocumentElementVisitor visitor)
        {
            visitor.Visit(this);
        }
    }

    public class ParagraphElement : DocumentElement
    {
        public int CharNum { get; set; }
        public int WordNum { get; set; }

        public ParagraphElement(int charNum, int wordNum)
        {
            CharNum = charNum;
            WordNum = wordNum;
        }

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

這里,Accept方法就是Visitor模式的精髓,通過調(diào)用被訪問基類的Accept方法,被訪問基類通過語言的單分派,動態(tài)綁定了正確的被訪問子類,接著在子類方法中,將第一參數(shù)當(dāng)做執(zhí)行對象再調(diào)用一次它的方法,根據(jù)語言的單分派機(jī)制,第一參數(shù)也能被正確的動態(tài)綁定類型,這樣就實(shí)現(xiàn)了雙分派

這就是Visitor模式的簡單介紹,這個模式的好處在于:

  • 克服語言沒有雙分派功能的缺陷,能夠正確的解析參數(shù)的類型,尤其當(dāng)想要對一個繼承族群類的不同子類定制訪問方法時,這個模式可以派上用場
  • 非常便于添加訪問者,試想,如果我們未來想要添加一個DocumentPriceCount,需要對段落和圖片計(jì)費(fèi),我們只需要新建一個類,繼承自DocumentVisitor,同時實(shí)現(xiàn)相應(yīng)的Visit方法就行

希望大家通過這篇文章,能對Visitor模式有一定了解,在實(shí)踐中可以恰當(dāng)?shù)氖褂谩?/p>

總結(jié)

到此這篇關(guān)于C#中Visitor模式的文章就介紹到這了,更多相關(guān)C# Visitor模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • WPF調(diào)用Matlab函數(shù)的方法

    WPF調(diào)用Matlab函數(shù)的方法

    這篇文章主要為大家詳細(xì)介紹了WPF調(diào)用Matlab函數(shù)的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-11-11
  • C#編寫一個簡單記事本功能

    C#編寫一個簡單記事本功能

    這篇文章主要為大家詳細(xì)介紹了C#編寫一個簡單記事本功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-10-10
  • 基于C#實(shí)現(xiàn)員工IC卡的讀寫功能

    基于C#實(shí)現(xiàn)員工IC卡的讀寫功能

    這篇文章主要為大家詳細(xì)介紹了C#如何實(shí)現(xiàn)讀寫員工IC卡的功能,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)C#有一定的幫助,感興趣的小伙伴可以跟隨小編一起了解一下
    2023-01-01
  • C#多線程用法詳解

    C#多線程用法詳解

    本文詳細(xì)講解了C#多線程用法,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-12-12
  • C#自動類型轉(zhuǎn)換與強(qiáng)制類型轉(zhuǎn)換的講解

    C#自動類型轉(zhuǎn)換與強(qiáng)制類型轉(zhuǎn)換的講解

    今天小編就為大家分享一篇關(guān)于C#自動類型轉(zhuǎn)換與強(qiáng)制類型轉(zhuǎn)換的講解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-01-01
  • c# 解決IIS寫Excel的權(quán)限問題

    c# 解決IIS寫Excel的權(quán)限問題

    使用以上方法必須對dcom進(jìn)行配置,給用戶使用office的權(quán)限
    2012-10-10
  • C#動態(tài)加載dll擴(kuò)展系統(tǒng)功能的方法

    C#動態(tài)加載dll擴(kuò)展系統(tǒng)功能的方法

    這篇文章主要介紹了C#動態(tài)加載dll擴(kuò)展系統(tǒng)功能的方法,涉及C#動態(tài)加載dll擴(kuò)展的相關(guān)技巧,需要的朋友可以參考下
    2015-04-04
  • C#6 null 條件運(yùn)算符

    C#6 null 條件運(yùn)算符

    本文主要對比C# 6 null運(yùn)算符與老版本的不同,并且用代碼實(shí)例測試,發(fā)現(xiàn)新語法性能提高,語法簡化了。希望看到的同學(xué)對你有所幫助
    2016-07-07
  • C#中變量、常量、枚舉、預(yù)處理器指令知多少

    C#中變量、常量、枚舉、預(yù)處理器指令知多少

    這篇文章主要介紹了c#共有其中變量類型有:靜態(tài)變量、實(shí)類變量、數(shù)組元素、數(shù)值參數(shù)、引用參數(shù)、輸出參數(shù)和局部變量,需要的朋友可以參考一下
    2017-04-04
  • WPF實(shí)現(xiàn)帶篩選功能的DataGrid

    WPF實(shí)現(xiàn)帶篩選功能的DataGrid

    在默認(rèn)情況下,WPF提供的DataGrid僅擁有數(shù)據(jù)展示等簡單功能,如果要實(shí)現(xiàn)像Excel一樣復(fù)雜的篩選過濾功能,則相對比較麻煩。本文以一個簡單的小例子,簡述如何通過WPF實(shí)現(xiàn)DataGrid的篩選功能,僅供學(xué)習(xí)分享使用,如有不足之處,還請指正
    2023-03-03

最新評論

宜章县| 红安县| 商洛市| 横山县| 扶风县| 长沙县| 岑溪市| 湛江市| 西平县| 武城县| 无极县| 翁牛特旗| 博兴县| 胶南市| 彰武县| 德州市| 宣汉县| 曲靖市| 洛南县| 拉萨市| 中卫市| 达州市| 丹寨县| 扶绥县| 浑源县| 西贡区| 剑川县| 来凤县| 清丰县| 隆回县| 明光市| 法库县| 禄丰县| 铁力市| 贺州市| 舒城县| 富平县| 定南县| 南涧| 祁阳县| 紫阳县|