這一次數(shù)據(jù)說了算省古,『訪問者模式』

目錄:設(shè)計模式之小試牛刀
源碼路徑:Github-Design Pattern


定義:(Visitor Pattern)

封裝一些作用于某種數(shù)據(jù)結(jié)構(gòu)中的各元素的操作叼风,它可以在不改變數(shù)據(jù)結(jié)構(gòu)的前提下定義作用于這些元素的新的操作。
換句話說:
訪問者模式賦予了【數(shù)據(jù)】的選擇權(quán)。
一般而言假夺,我們都是直接通過【數(shù)據(jù)操作類】操作【數(shù)據(jù)】。
而通過訪問者模式斋攀,【數(shù)據(jù)】可以選擇某個【數(shù)據(jù)操作類】來訪問它已卷。

類圖:

訪問者模式通用類圖

啟示:

現(xiàn)在的互聯(lián)網(wǎng)時代真是給我們提供了極大的便利。出門不用帶現(xiàn)金了淳蔼,買票不用本人到火車站了侧蘸,水電費手機上就繳了,網(wǎng)上購物直郵到家了鹉梨,吃飯也不用下樓了讳癌。
慢著,似乎我要跑題了俯画。
這節(jié)可是要講訪問者模式析桥,跟互聯(lián)網(wǎng)有半毛錢關(guān)系。
別急關(guān)系是硬扯的艰垂。

正如六度空間理論,又名六度分隔理論泡仗。
你至多只要通過六個人就能認識全世界的任意一個人。

這咋一聽是不很玄乎猜憎。
舉個例子娩怎,就像你跟隔壁村的老王扯關(guān)系一樣,最終還是能扯上點親戚關(guān)系的胰柑。

下面我們就開始正二八經(jīng)的扯吧截亦。
我們就以淘寶購物為例來進行訪問者模式的思考。

想一想我們在淘寶下單支付之后柬讨,淘寶做了什么崩瓤?
是不是需要撿貨發(fā)貨?
對于揀貨員來說踩官,需要根據(jù)訂單進行揀貨却桶。
對于發(fā)貨員來說,需要根據(jù)訂單的收貨信息蔗牡,進行快遞發(fā)貨颖系。
....
就從以上場景來說,針對一張訂單辩越,已經(jīng)有兩個不同訪問者嘁扼。
每個訪問者訪問訂單的不同數(shù)據(jù),做成不同的操作黔攒。

好了趁啸,廢話不多說强缘,咱們代碼見。

代碼:

假設(shè)淘寶后臺有一個訂單中心莲绰,負責訂單相關(guān)業(yè)務(wù)的流轉(zhuǎn)欺旧。訂單一般上而言主要包括兩種,銷售訂單蛤签、退貨訂單辞友。

根據(jù)以上購物場景,我們簡單抽象出以下幾個對象:

  • Product:商品類
  • Customer:客戶類
  • Order:訂單類(SaleOrder:銷售訂單震肮、ReturnOrder:退貨訂單)
  • OrderLine:訂單分錄類
  • Picker:揀貨員
  • Distributor:發(fā)貨員
  • OrderCenter:訂單中心

客戶類主要包含簡單的個人信息和收貨信息:

/// <summary>
/// 客戶類
/// </summary>
public class Customer
{
    public int Id { get; set; }
    public string NickName { get; set; }
    public string RealName { get; set; }
    public string Phone { get; set; }
    public string Address { get; set; }
    public string Zip { get; set; }
}

產(chǎn)品類簡單包含產(chǎn)品名稱称龙、價格信息:

/// <summary>
/// 產(chǎn)品類
/// </summary>
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual decimal Price { get; set; }
}

下面來看看訂單相關(guān)類:

/// <summary>
/// 訂單抽象類
/// </summary>
public abstract class Order
{
    public int Id { get; set; }
    public Customer Customer { get; set; }
    public DateTime CreatorDate { get; set; }

    /// <summary>
    /// 單據(jù)品項
    /// </summary>
    public List<OrderLine> OrderItems { get; set; }
    public abstract void Accept(Visitor visitor);

}

/// <summary>
/// 銷售訂單
/// </summary>
public class SaleOrder : Order
{
    public override void Accept(Visitor visitor)
    {
        visitor.Visit(this);
    }
}

/// <summary>
/// 退貨單
/// </summary>
public class ReturnOrder : Order
{
    public override void Accept(Visitor visitor)
    {
        visitor.Visit(this);
    }
}

public class OrderLine
{
    public int Id { get; set; }
    public Product Product { get; set; }
    public int Qty { get; set; }
}

其中Order類定義了一個抽象方法Accept(Visitor visitor);,子類通過visitor.Visit(this)直接簡單重載戳晌。

下面我們來看下訪問者角色的定義:

 /// <summary>
 /// 訪問者
 /// </summary>
 public abstract class Visitor
 {
     public abstract void Visit(SaleOrder saleOrder);
     public abstract void Visit(ReturnOrder returnOrder);
 }

其中主要定義了兩個抽象Visit方法鲫尊,用來分別對SaleOrderReturnOrder進行處理。

接下來我們就來看看具體的訪問者的實現(xiàn)吧:

/// <summary>
/// 撿貨員
/// 對銷售訂單沦偎,從倉庫撿貨疫向。
/// 對退貨訂單,將收到的貨品歸放回倉庫豪嚎。
/// </summary>
public class Picker : Visitor
{
    public int Id { get; set; }
    public string Name { get; set; }

    public override void Visit(SaleOrder saleOrder)
    {
        Console.WriteLine($"開始為銷售訂單【{saleOrder.Id}】進行銷售撿貨處理:");
        foreach (var item in saleOrder.OrderItems)
        {
            Console.WriteLine($"【{item.Product.Name}】商品* {item.Qty}");
        }

        Console.WriteLine($"訂單【{saleOrder.Id}】撿貨完畢搔驼!");

        Console.WriteLine("==========================");
    }

    public override void Visit(ReturnOrder returnOrder)
    {
        Console.WriteLine($"開始為退貨訂單【{returnOrder.Id}】進行退貨撿貨處理:");
        foreach (var item in returnOrder.OrderItems)
        {
            Console.WriteLine($"【{item.Product.Name}】商品* {item.Qty}");
        }

        Console.WriteLine($"退貨訂單【{returnOrder.Id}】退貨撿貨完畢!", returnOrder.Id);
        Console.WriteLine("==========================");
    }
}

/// <summary>
/// 收發(fā)貨員
/// 對銷售訂單侈询,進行發(fā)貨處理
/// 對退貨訂單舌涨,進行收貨處理
/// </summary>
public class Distributor : Visitor
{
    public int Id { get; set; }
    public string Name { get; set; }

    public override void Visit(SaleOrder saleOrder)
    {
        Console.WriteLine($"開始為銷售訂單【{saleOrder.Id}】進行發(fā)貨處理:", saleOrder.Id);

        Console.WriteLine($"一共打包{saleOrder.OrderItems.Sum(line => line.Qty)}件商品。");
        Console.WriteLine($"收貨人:{saleOrder.Customer.RealName}");
        Console.WriteLine($"聯(lián)系電話:{saleOrder.Customer.Phone}");
        Console.WriteLine($"收貨地址:{saleOrder.Customer.Address}");
        Console.WriteLine($"郵政編碼:{saleOrder.Customer.Zip}");

        Console.WriteLine($"訂單【{saleOrder.Id}】發(fā)貨完畢扔字!" );
        Console.WriteLine("==========================");
    }

    public override void Visit(ReturnOrder returnOrder)
    {
        Console.WriteLine($"收到來自【{returnOrder.Customer.NickName}】的退貨訂單【{returnOrder.Id}】囊嘉,進行退貨收貨處理:");

        foreach (var item in returnOrder.OrderItems)
        {
            Console.WriteLine($"【{item.Product.Name}】商品* {item.Qty}" );
        }

        Console.WriteLine($"退貨訂單【{returnOrder.Id}】收貨處理完畢!" );
        Console.WriteLine("==========================");
    }
}

代碼中已經(jīng)寫的夠清楚了革为,我就不多說了扭粱。

最后上下我們的訂單中心的代碼:

/// <summary>
/// 訂單中心
/// </summary>
public class OrderCenter : List<Order>
{
    public void Accept(Visitor visitor)
    {
        var iterator = this.GetEnumerator();

        while (iterator.MoveNext())
        {
            iterator.Current.Accept(visitor);
        }
    }

}

OrderCenter就是簡單的集合類,提供了一個Accept(Visitor visitor)方法來指定接受哪一種訪問者訪問震檩。

看看場景類:

static void Main(string[] args)
{
    Customer customer = new Customer
    {
        Id = 1,
        NickName = "圣杰",
        RealName = "圣杰",
        Address = "深圳市南山區(qū)",
        Phone = "135****9358",
        Zip = "518000"
    };

    Product productA = new Product { Id = 1, Name = "小米5", Price = 1899 };
    Product productB = new Product { Id = 2, Name = "小米5手機防爆膜", Price = 29 };
    Product productC = new Product { Id = 3, Name = "小米5手機保護套", Price = 69 };

    OrderLine line1 = new OrderLine { Id = 1, Product = productA, Qty = 1 };
    OrderLine line2 = new OrderLine { Id = 1, Product = productB, Qty = 2 };
    OrderLine line3 = new OrderLine { Id = 1, Product = productC, Qty = 3 };

    //先買了個小米5和防爆膜
    SaleOrder order1 = new SaleOrder { Id = 1, Customer = customer, CreatorDate = DateTime.Now, OrderItems = new List<OrderLine> { line1, line2 } };

    //又買了個保護套
    SaleOrder order2 = new SaleOrder { Id = 2, Customer = customer, CreatorDate = DateTime.Now, OrderItems = new List<OrderLine> { line3 } };

    //把保護套都退了
    ReturnOrder returnOrder = new ReturnOrder { Id = 3, Customer = customer, CreatorDate = DateTime.Now, OrderItems = new List<OrderLine> { line3 } };

    OrderCenter orderCenter = new OrderCenter { order1, order2, returnOrder };


    Picker picker = new Picker { Id = 110, Name = "撿貨員110" };

    Distributor distributor = new Distributor { Id = 111, Name = "發(fā)貨貨員111" };

    //撿貨員訪問訂單中心
    orderCenter.Accept(picker);

    //發(fā)貨員訪問訂單中心
    orderCenter.Accept(distributor);

    Console.ReadLine();
}
執(zhí)行結(jié)果

總結(jié):

從上例我們結(jié)合訪問者模式的通用類圖琢蛤,來理一理主要的幾個角色:

  • Visitor(抽象訪問者)
    抽象類或者接口,聲明訪問者可以訪問哪些元素恳蹲,具體到程序中就是Visit方法的參數(shù)定義哪些對象是可以被訪問的。
  • ConcreteVisitor(具體訪問者)
    用來定義訪問者訪問到具體類的行為俩滥。
    例子中就是我們的PickerDistributor嘉蕾。我們在撿貨員和發(fā)貨員分別定義了處理銷售訂單和退貨訂單的行為。
  • Element(抽象元素)
    接口或者抽象類霜旧,一般通過定義抽象Accept方法错忱,由子類指定接受哪一種訪問者訪問儡率。
    例子中就是我們的Order類。
  • ConcreteElement(具體元素)
    通過調(diào)用visitor.Visit(this)實現(xiàn)父類定義的抽象Accept方法以清。
    例子中儿普,SaleOrderReturnOrder就是這樣做的。
  • ObjectStruture(結(jié)構(gòu)對象)
    抽象元素的容器掷倔。
    例子中對應(yīng)的是訂單中心OrderCenter維護的一個Order集合眉孩。

優(yōu)缺點:

  • 符合SRP(單一職責原則),具體元素負責數(shù)據(jù)的存儲勒葱,訪問者負責數(shù)據(jù)的操作浪汪。
  • 擴展性好靈活性高,假如我們現(xiàn)在有財務(wù)要根據(jù)訂單來核查財務(wù)了凛虽。我們只需要實現(xiàn)一個財務(wù)的訪問者就好了死遭。
  • 不符合LKP(迪米特原則),訪問者訪問的具體元素內(nèi)容全部暴露給了訪問者凯旋。比如本例中呀潭,撿貨員和發(fā)貨員是沒必要知道商品的價格信息的。
  • 不符合OCP(開放封閉原則)至非,如果要更改具體的某個元素钠署,可能就需要修改到涉及到的所有訪問者。
  • 不符合DIP(依賴倒置原則)睡蟋,訪問者依賴的是具體的元素而不是抽象元素踏幻。這樣就會導(dǎo)致擴展訪問者比較困難。

應(yīng)用場景:

  • 適用于已確定訪問者方法的情況戳杀,否則后續(xù)更改會需要對訪問者進行更改该面。
  • 適用于重構(gòu)時使用。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末信卡,一起剝皮案震驚了整個濱河市隔缀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌傍菇,老刑警劉巖猾瘸,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異丢习,居然都是意外死亡牵触,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門咐低,熙熙樓的掌柜王于貴愁眉苦臉地迎上來揽思,“玉大人,你說我怎么就攤上這事见擦《ず梗” “怎么了羹令?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長损痰。 經(jīng)常有香客問我福侈,道長,這世上最難降的妖魔是什么卢未? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任肪凛,我火速辦了婚禮,結(jié)果婚禮上尝丐,老公的妹妹穿的比我還像新娘显拜。我一直安慰自己,他們只是感情好爹袁,可當我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布远荠。 她就那樣靜靜地躺著,像睡著了一般失息。 火紅的嫁衣襯著肌膚如雪譬淳。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天盹兢,我揣著相機與錄音邻梆,去河邊找鬼。 笑死绎秒,一個胖子當著我的面吹牛浦妄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播见芹,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼剂娄,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了玄呛?” 一聲冷哼從身側(cè)響起阅懦,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎徘铝,沒想到半個月后耳胎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡惕它,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年怕午,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片淹魄。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡郁惜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出揭北,到底是詐尸還是另有隱情扳炬,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布搔体,位于F島的核電站恨樟,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏疚俱。R本人自食惡果不足惜劝术,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望呆奕。 院中可真熱鬧养晋,春花似錦、人聲如沸梁钾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽姆泻。三九已至零酪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拇勃,已是汗流浹背四苇。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留方咆,地道東北人月腋。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像瓣赂,于是被迫代替她去往敵國和親榆骚。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容