定义:(Visitor Pattern)

封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。
换句话说:
访问者模式赋予了【数据】的选择权。
一般而言,我们都是直接通过【数据操作类】操作【数据】。
而通过访问者模式,【数据】可以选择某个【数据操作类】来访问它。

类图:

启示

现在的互联网时代真是给我们提供了极大的便利。出门不用带现金了,买票不用本人到火车站了,水电费手机上就缴了,网上购物直邮到家了,吃饭也不用下楼了。
慢着,似乎我要跑题了。
这节可是要讲访问者模式,跟互联网有半毛钱关系。
别急关系是硬扯的。

正如六度空间理论,又名六度分隔理论。
你至多只要通过六个人就能认识全世界的任意一个人。

这咋一听是不很玄乎。
举个例子,就像你跟隔壁村的老王扯关系一样,最终还是能扯上点亲戚关系的。

下面我们就开始正二八经的扯吧。
我们就以淘宝购物为例来进行访问者模式的思考。

想一想我们在淘宝下单支付之后,淘宝做了什么?
是不是需要捡货发货?
对于拣货员来说,需要根据订单进行拣货。
对于发货员来说,需要根据订单的收货信息,进行快递发货。
....
就从以上场景来说,针对一张订单,已经有两个不同访问者。
每个访问者访问订单的不同数据,做成不同的操作。

好了,废话不多说,咱们代码见。

代码

假设淘宝后台有一个订单中心,负责订单相关业务的流转。订单一般上而言主要包括两种,销售订单、退货订单。

根据以上购物场景,我们简单抽象出以下几个对象:

  • Product:商品类
  • Customer:客户类
  • Order:订单类(SaleOrder:销售订单、ReturnOrder:退货订单)
  • OrderLine:订单分录类
  • Picker:拣货员
  • Distributor:发货员
  • 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; }
}

产品类简单包含产品名称、价格信息:

/// <summary>
/// 产品类
/// </summary>
public class Product
{public int Id { get; set; }public string Name { get; set; }public virtual decimal Price { get; set; }
}

下面来看看订单相关类:

/// <summary>
/// 订单抽象类
/// </summary>
public abstract class Order
{public int Id { get; set; }public Customer Customer { get; set; }public DateTime CreatorDate { get; set; }/// <summary>/// 单据品项/// </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进行处理。

接下来我们就来看看具体的访问者的实现吧:

/// <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>
/// 收发货员
/// 对销售订单,进行发货处理
/// 对退货订单,进行收货处理
/// </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}】进行发货处理:", saleOrder.Id);Console.WriteLine($"一共打包{saleOrder.OrderItems.Sum(line => line.Qty)}件商品。");Console.WriteLine($"收货人:{saleOrder.Customer.RealName}");Console.WriteLine($"联系电话:{saleOrder.Customer.Phone}");Console.WriteLine($"收货地址:{saleOrder.Customer.Address}");Console.WriteLine($"邮政编码:{saleOrder.Customer.Zip}");Console.WriteLine($"订单【{saleOrder.Id}】发货完毕!" );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("==========================");}
}

代码中已经写的够清楚了,我就不多说了。

最后上下我们的订单中心的代码:

/// <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 = "深圳市南山区",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 = "发货货员111" };//捡货员访问订单中心orderCenter.Accept(picker);//发货员访问订单中心orderCenter.Accept(distributor);Console.ReadLine();
}

总结:

从上例我们结合访问者模式的通用类图,来理一理主要的几个角色:

  • Visitor(抽象访问者)
    抽象类或者接口,声明访问者可以访问哪些元素,具体到程序中就是Visit方法的参数定义哪些对象是可以被访问的。
  • ConcreteVisitor(具体访问者)
    用来定义访问者访问到具体类的行为。
    例子中就是我们的PickerDistributor。我们在捡货员和发货员分别定义了处理销售订单和退货订单的行为。
  • Element(抽象元素)
    接口或者抽象类,一般通过定义抽象Accept方法,由子类指定接受哪一种访问者访问。
    例子中就是我们的Order类。
  • ConcreteElement(具体元素)
    通过调用visitor.Visit(this)实现父类定义的抽象Accept方法。
    例子中,SaleOrderReturnOrder就是这样做的。
  • ObjectStruture(结构对象)
    抽象元素的容器。
    例子中对应的是订单中心OrderCenter维护的一个Order集合。

优缺点:

  • 符合SRP(单一职责原则),具体元素负责数据的存储,访问者负责数据的操作。
  • 扩展性好灵活性高,假如我们现在有财务要根据订单来核查财务了。我们只需要实现一个财务的访问者就好了。
  • 不符合LKP(迪米特原则),访问者访问的具体元素内容全部暴露给了访问者。比如本例中,捡货员和发货员是没必要知道商品的价格信息的。
  • 不符合OCP(开放封闭原则),如果要更改具体的某个元素,可能就需要修改到涉及到的所有访问者。
  • 不符合DIP(依赖倒置原则),访问者依赖的是具体的元素而不是抽象元素。这样就会导致扩展访问者比较困难。

应用场景:

  • 适用于已确定访问者方法的情况,否则后续更改会需要对访问者进行更改。
  • 适用于重构时使用。

源码:

源代码C# GitHub

系列导航:

设计模式之小试牛刀

这一次数据说了算,『访问者模式』相关推荐

  1. 牵线搭桥,『桥接模式』

    目录:设计模式之小试牛刀 源码路径:Github-Design Pattern 定义:(Bridge Pattern) 将抽象和实现解耦,使得两者可以独立地变化. 类图: 启示: 一个产品的研发,流程 ...

  2. 创建相似对象,就交给『工厂模式』吧

    源码: 源代码C# 系列导航: 目录 定义(Factory Pattern): 用来创建目标对象的类,将相似对象的创建工作统一到一个类来完成. 一.简单工厂模式: 代码: /// <summar ...

  3. 『设计模式』我能进来坐坐吗?--访问者模式

    23种设计模式+额外常用设计模式汇总 (持续更新) 访问者模式 访问者( Visitor )模式的定义: 将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添 ...

  4. Java设计模式:访问者模式,同一数据对象,不同访问者索取目的不同

    /*** 员工接受不同部门的数据访问. * 但是不同部门访问该名员工获取的数据不相同.* * @author zhangfly** @param <T>*/ public abstract ...

  5. 行为型模式:访问者模式

    前方高能:<一故事一设计模式>PDF 电子书已经上线,关注公众号即可获取. 文章首发: 行为型模式:访问者模式 十一大行为型模式之十一:访问者模式. 简介 姓名 :访问者模式 英文名 :V ...

  6. 设计模式之访问者模式、例子分析

    1. 定义 访问者模式( Visitor):表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作 2. 使用前提 这个模式是很复杂的模式,一般情况下 ...

  7. PHP设计模式之访问者模式

    访问者模式实际上是让外部类能够获取树形结构的每个节点的对象,对每个对象进行操作的模式,它让我们在不改动原有树形结构的基础上扩展功能,比如统计等等. 在这种模式下,必须有的几个要素: 1.具体的元素对象 ...

  8. Java设计模式(访问者模式-迭代器模式-观察者模式-中介者模式)

    Java设计模式Ⅶ 1.访问者模式 1.1 访问者模式概述 1.2 代码理解 2.迭代器模式 2.1 迭代器模式概述 2.2 代码理解 3.观察者模式 3.1 观察者模式概述 3.2 代码理解 4.中 ...

  9. 设计模式 — 行为型模式 — 访问者模式

    目录 文章目录 目录 访问者模式(Visitor Pattern) 应用场景 代码示例 访问者模式(Visitor Pattern) 数据结构中保存着许多元素,当我们希望改变一种对元素的处理方式时,要 ...

最新文章

  1. android4.4 添加快捷开关(以截屏为例)
  2. 农村程序员吐槽:虽然挣着2万高薪,但却舍不得吃舍不得穿
  3. Servlet学习DAY_01:服务器概念/Web服务器的作用/ Servlet概念/ 如何关联和解除Tomcat/ 创建一个Web工程 /Servlet响应流程/ Get-Post /常见异常
  4. 01-缓存一致性---基础知识
  5. boot分布式计算 spring_springboot05-分布式系统理念
  6. vhd 镜像 备份Linux,差分VHD 系统秒备份、秒还原教程 完胜GHOST
  7. visualSVN下载与安装
  8. java.library.path到底指什么
  9. istio回归「单体应用」对我们的启发
  10. 天人短文网站系统v5.53源码
  11. JWT教程_3 oauth和JWT 整合
  12. 修改数据库的排序规则
  13. 深度学习笔记_卷积神经网络基本概念
  14. 重邮学报和计算机工程与应用,重庆邮电大学学报
  15. 什么是ANC降噪技术?耳机工厂来告诉你
  16. namecheap注册域名优惠码
  17. 建立大数据分析能力需四大要素
  18. yolov3模型部署实战weights转onnx并推理
  19. 【Unity3D】Unity3D 软件安装 ( 注册账号并下载 Unity Hub | 安装 Unity Hub | 获取个人版授权 | 中文环境设置 | 安装 Unity3D 编辑器 )
  20. Python 抓取淘宝联盟优惠券

热门文章

  1. 记一次CSDN的资源加载失败的问题的解决方法
  2. Ignite简介以及和Coherence、Gemfire、Redis等的比较
  3. mysql字符串排序
  4. 微信公众号开发笔记(九)发送语音消息
  5. 武汉大学igs(FTP)
  6. MP3TAG:ID3V2
  7. Qt 关于窗口全屏显示与退出全屏的实现
  8. Node EE方案 -- Rockerjs在微店的建设与发展
  9. 极限编程(XP)的12个实践原则
  10. linux防火墙策略