定義:(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
方法鲫尊,用來分別對SaleOrder
和ReturnOrder
進行處理。
接下來我們就來看看具體的訪問者的實現(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();
}
總結(jié):
從上例我們結(jié)合訪問者模式的通用類圖琢蛤,來理一理主要的幾個角色:
- Visitor(抽象訪問者)
抽象類或者接口,聲明訪問者可以訪問哪些元素恳蹲,具體到程序中就是Visit方法的參數(shù)定義哪些對象是可以被訪問的。
- ConcreteVisitor(具體訪問者)
用來定義訪問者訪問到具體類的行為俩滥。
例子中就是我們的Picker
和Distributor
嘉蕾。我們在撿貨員和發(fā)貨員分別定義了處理銷售訂單和退貨訂單的行為。 - Element(抽象元素)
接口或者抽象類霜旧,一般通過定義抽象Accept
方法错忱,由子類指定接受哪一種訪問者訪問儡率。
例子中就是我們的Order
類。 - ConcreteElement(具體元素)
通過調(diào)用visitor.Visit(this)
實現(xiàn)父類定義的抽象Accept
方法以清。
例子中儿普,SaleOrder
和ReturnOrder
就是這樣做的。 - 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)時使用。