北风设计模式课程---访问者模式(Visitor)

一、总结

一句话总结:

设计模式是日常问题的经验总结方案,所以学好设计模式对日常出现的问题可以有很好的解决。
访问者设计模式有点神似 抽象工厂模式,都是增加一个维度很容易,增加另外一个维度很麻烦

1、访问者模式实例?

1、在患者就医时,医生会根据病情开具处方单,很多医院都会存在以下这个流程:划价人员拿到处方单之后根据药品名称和数量计算总价,而药房工作人员根据药品名称和数量准备药品,
2、在软件开发中,有时候也需要处理像处方单这样的集合对象结构,在该对象结构中存储了多个不同类型的对象信息,而且对同一对象结构中的元素的操作方式并不唯一,可能需要提供多种不同的处理方式。在设计模式中,有一种模式可以满足上述要求,其模式动机就是以不同的方式操作复杂对象结构,该模式就是访问者模式。

2、访问者模式简介?

1、访问者模式是一种较为复杂的行为型模式,它包含【访问者和被访问元素】两个主要组成部分,这些被访问的元素通常具有不同的类型,且【不同的访问者可以对它们进行不同的访问操作】。例如:【处方单中的各种药品信息就是被访问的元素,而划价人员和药房工作人员就是访问者】。访问者模式可以使得用户在不修改现有系统的情况下扩展系统的功能,为这些不同类型的元素增加新的操作。
2、提供一个作用于某对象结构中的各元素的操作表示,它使得可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

3、访问者模式主要角色?

1、Visitor(抽象访问者)/ConcreteVisitor(具体访问者):抽象访问者为对象结构中每一个具体元素类ConcreteElement声明一个访问操作,从这个操作的名称或参数类型可以清楚知道需要访问的具体元素的类型,具体访问者则需要实现这些操作方法,定义对这些元素的访问操作。具体访问者实现了抽象访问者声明的方法,每一个操作作用于访问对象结构中一种类型的元素。
2、Element(抽象元素)/ConcreteElement(具体元素):一般是一个抽象类或接口,定义一个Accept方法,该方法通常以一个抽象访问者作为参数。具体元素实现了Accept方法,在Accept方法中调用访问者的访问方法以便完成一个元素的操作。
3、ObjectStructure(对象结构):对象结构是一个元素的集合,用于存放元素对象,且提供便利其内部元素的方法。

  (1)Visitor(抽象访问者):抽象访问者为对象结构中每一个具体元素类ConcreteElement声明一个访问操作,从这个操作的名称或参数类型可以清楚知道需要访问的具体元素的类型,具体访问者则需要实现这些操作方法,定义对这些元素的访问操作。
  (2)ConcreteVisitor(具体访问者):具体访问者实现了抽象访问者声明的方法,每一个操作作用于访问对象结构中一种类型的元素。
  (3)Element(抽象元素):一般是一个抽象类或接口,定义一个Accept方法,该方法通常以一个抽象访问者作为参数。
  (4)ConcreteElement(具体元素):具体元素实现了Accept方法,在Accept方法中调用访问者的访问方法以便完成一个元素的操作。
  (5)ObjectStructure(对象结构):对象结构是一个元素的集合,用于存放元素对象,且提供便利其内部元素的方法。

4、访问者模式 实现 重构OA系统员工数据 的角色实例?

1、FinanceDepartment表示财务部,HRDepartment表示人力资源部,它们充当具体访问者的角色,其抽象父类Department充当抽象访问者角色;
2、EmployeeList充当对象结构,用于存储员工列表;
3、FullTimeEmployee表示全职员工,PartTimeEmployee表示兼职员工,它们充当具体元素角色,而其父类IEmployee(这里实现形式是interface)充当抽象元素角色。

5、访问者模式中的对象结构类示例?

|||-begin

/// <summary>/// 对象结构类:EmployeeList/// </summary>public class EmployeeList{private IList<IEmployee> empList = new List<IEmployee>();public void AddEmployee(IEmployee emp){this.empList.Add(emp);}public void Accept(Department handler){foreach (var emp in empList){emp.Accept(handler);}}

|||-end

private IList<IEmployee> empList = new List<IEmployee>();:用于存储员工列表

6、访问模式的主要优缺点?

1、增加新的访问操作十分方便,不痛不痒 => 符合开闭原则
2、增加新的元素类很困难,需要在每一个访问者类中增加相应访问操作代码 => 违背了开闭原则

7、适合多维的设计模式?

访问者设计模式,抽象工厂设计模式,桥接模式,外观模式

8、访问者模式 主要优点?

(1)增加新的访问操作十分方便,不痛不痒 => 符合开闭原则
(2)将有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个的元素类中,类的职责更加清晰 => 符合单一职责原则

9、访问者模式 主要缺点?

(1)增加新的元素类很困难,需要在每一个访问者类中增加相应访问操作代码 => 违背了开闭原则
(2)元素对象有时候必须暴露一些自己的内部操作和状态,否则无法供访问者访问 => 破坏了元素的封装性

10、访问者模式 应用场景?

(1)一个对象结构包含多个类型的对象,希望对这些对象实施一些依赖其具体类型的操作。=> 不同的类型可以有不同的访问操作
(2)对象结构中对象对应的类很少改变 很少改变 很少改变(重要的事情说三遍),但经常需要在此对象结构上定义新的操作。

11、访问者模式实例?

- M公司开发部想要为某企业开发一个OA系统,在该OA系统中包含一个员工信息管理子系统,该企业包括正式员工和临时工,每周HR部门和财务部等部门需要对员工数据进行汇总,汇总数据包括员工工作时间、员工工资等等。该企业的基本制度如下:
- (1)正式员工(Full time Employee)每周工作时间为40小时,不同级别、不同部门的员工每周基本工资不同;如果超过40小时,超出部分按照100元/小时作为加班费;如果少于40小时,所缺时间按照请假处理,请假锁扣工资以80元/小时计算,直到基本工资扣除到0为止。除了记录实际工作时间外,HR部需要记录加班时长或请假时长,作为员工平时表现的一项依据。
- (2)临时员工(Part time Employee)每周工作时间不固定,基本工资按照小时计算,不同岗位的临时工小时工资不同。HR部只需要记录实际工作时间。
- HR人力资源部和财务部工作人员可以根据各自的需要对员工数据进行汇总处理,HR人力资源部负责汇总每周员工工作时间,而财务部负责计算每周员工工资。

二、设计模式的征途—16.访问者(Visitor)模式

转自或参考:设计模式的征途—16.访问者(Visitor)模式
https://www.cnblogs.com/edisonchou/p/7247990.html

在患者就医时,医生会根据病情开具处方单,很多医院都会存在以下这个流程:划价人员拿到处方单之后根据药品名称和数量计算总价,而药房工作人员根据药品名称和数量准备药品,如下图所示。

在软件开发中,有时候也需要处理像处方单这样的集合对象结构,在该对象结构中存储了多个不同类型的对象信息,而且对同一对象结构中的元素的操作方式并不唯一,可能需要提供多种不同的处理方式。在设计模式中,有一种模式可以满足上述要求,其模式动机就是以不同的方式操作复杂对象结构,该模式就是访问者模式。

访问者模式(Visitor) 学习难度:★★★★☆ 使用频率:★☆☆☆☆

一、OA系统员工数据汇总设计

1.1 需求背景

Background:M公司开发部想要为某企业开发一个OA系统,在该OA系统中包含一个员工信息管理子系统,该企业包括正式员工和临时工,每周HR部门和财务部等部门需要对员工数据进行汇总,汇总数据包括员工工作时间、员工工资等等。该企业的基本制度如下:

(1)正式员工(Full time Employee)每周工作时间为40小时,不同级别、不同部门的员工每周基本工资不同;如果超过40小时,超出部分按照100元/小时作为加班费;如果少于40小时,所缺时间按照请假处理,请假锁扣工资以80元/小时计算,直到基本工资扣除到0为止。除了记录实际工作时间外,HR部需要记录加班时长或请假时长,作为员工平时表现的一项依据。

(2)临时员工(Part time Employee)每周工作时间不固定,基本工资按照小时计算,不同岗位的临时工小时工资不同。HR部只需要记录实际工作时间。

HR人力资源部和财务部工作人员可以根据各自的需要对员工数据进行汇总处理,HR人力资源部负责汇总每周员工工作时间,而财务部负责计算每周员工工资。

1.2 初始设计

  M公司开发人员针对需求,提出了一个初始的解决方案,其核心代码如下:

    public  class EmployeeList{// 员工集合private IList<Employee> empList = new List<Employee>();// 增加员工public void AddEmployee(Employee emp){this.empList.Add(emp);}// 处理员工数据public void Handle(string deptName){if (deptName.Equals("财务部")){foreach (var emp in empList){if (emp.GetType().Equals("FullTimeEmployee")){Console.WriteLine("财务部处理全职员工数据!");}else{Console.WriteLine("财务部处理兼职员工数据!");}}}else if (deptName.Equals("人力资源部")){foreach (var emp in empList){if (emp.GetType().Equals("FullTimeEmployee")){Console.WriteLine("人力资源部处理全职员工数据!");}else{Console.WriteLine("人力资源部处理兼职员工数据!");}}}}}

  不难发现,该解决方案存在以下问题:

  (1)EmployeeList类非常庞大,承担了过多的职责,既不便于代码复用,也不便于系统扩展,违背了单一职责原则。

  (2)包含了大量的if-else语句,测试和维护的难度增大。

  (3)如果要新增一个部门来操作员工数据集合,那么不得不修改EmployeeList类的源代码,违背了开闭原则。

  访问者模式是一个可以考虑用来解决的方案,它可以在一定程度上解决上述问题(大部分问题)。

二、访问者模式概述

2.1 访问者模式简介

  访问者模式是一种较为复杂的行为型模式,它包含访问者和被访问元素两个主要组成部分,这些被访问的元素通常具有不同的类型,且不同的访问者可以对它们进行不同的访问操作。例如:处方单中的各种药品信息就是被访问的元素,而划价人员和药房工作人员就是访问者。访问者模式可以使得用户在不修改现有系统的情况下扩展系统的功能,为这些不同类型的元素增加新的操作。

访问者(Visitor)模式:提供一个作用于某对象结构中的各元素的操作表示,它使得可以在不改变各元素的类的前提下定义作用于这些元素的新操作。访问者模式是一种对象行为型模式。

2.2 访问者模式结构

  访问者模式结构图中包含以下5个角色:

  (1)Visitor(抽象访问者):抽象访问者为对象结构中每一个具体元素类ConcreteElement声明一个访问操作,从这个操作的名称或参数类型可以清楚知道需要访问的具体元素的类型,具体访问者则需要实现这些操作方法,定义对这些元素的访问操作。

  (2)ConcreteVisitor(具体访问者):具体访问者实现了抽象访问者声明的方法,每一个操作作用于访问对象结构中一种类型的元素。

  (3)Element(抽象元素):一般是一个抽象类或接口,定义一个Accept方法,该方法通常以一个抽象访问者作为参数。

  (4)ConcreteElement(具体元素):具体元素实现了Accept方法,在Accept方法中调用访问者的访问方法以便完成一个元素的操作。

  (4)ObjectStructure(对象结构):对象结构是一个元素的集合,用于存放元素对象,且提供便利其内部元素的方法。

三、重构OA系统员工数据汇总

3.1 重构后的设计结构

  在上图中,FinanceDepartment表示财务部,HRDepartment表示人力资源部,它们充当具体访问者的角色,其抽象父类Department充当抽象访问者角色;EmployeeList充当对象结构,用于存储员工列表;FullTimeEmployee表示全职员工,PartTimeEmployee表示兼职员工,它们充当具体元素角色,而其父类IEmployee(这里实现形式是interface)充当抽象元素角色。

3.2 具体代码实现

  (1)抽象元素=>IEmployee

    /// <summary>/// 抽象元素类:Employee/// </summary>public interface IEmployee{void Accept(Department handler);}

  (2)具体元素=>FullTimeEmployee,PartTimeEmployee

    /// <summary>/// 具体元素类:FullTimeEmployee/// </summary>public class FullTimeEmployee : IEmployee{public string Name { get; set; }public double WeeklyWage { get; set; }public int WorkTime { get; set; }public FullTimeEmployee(string name, double weeklyWage, int workTime){this.Name = name;this.WeeklyWage = weeklyWage;this.WorkTime = workTime;}public void Accept(Department handler){handler.Visit(this);}}/// <summary>/// 具体元素类:PartTimeEmployee/// </summary>public class PartTimeEmployee : IEmployee{public string Name { get; set; }public double HourWage { get; set; }public int WorkTime { get; set; }public PartTimeEmployee(string name, double hourWage, int workTime){this.Name = name;this.HourWage = hourWage;this.WorkTime = workTime;}public void Accept(Department handler){handler.Visit(this);}}

  (3)对象结构=>EmployeeList

    /// <summary>/// 对象结构类:EmployeeList/// </summary>public class EmployeeList{private IList<IEmployee> empList = new List<IEmployee>();public void AddEmployee(IEmployee emp){this.empList.Add(emp);}public void Accept(Department handler){foreach (var emp in empList){emp.Accept(handler);}}

  (4)抽象访问者=>Department

    /// <summary>/// 抽象访问者类:Department/// </summary>public abstract class Department{// 声明一组重载的访问方法,用于访问不同类型的具体元素public abstract void Visit(FullTimeEmployee employee);public abstract void Visit(PartTimeEmployee employee);}

  (5)具体访问者=>FinanceDepartment,HRDepartment

    /// <summary>/// 具体访问者类:FinanceDepartment/// </summary>public class FinanceDepartment : Department{// 实现财务部对兼职员工数据的访问public override void Visit(PartTimeEmployee employee){int workTime = employee.WorkTime;double hourWage = employee.HourWage;Console.WriteLine("临时工 {0} 实际工资为:{1} 元", employee.Name, workTime * hourWage);}// 实现财务部对全职员工数据的访问public override void Visit(FullTimeEmployee employee){int workTime = employee.WorkTime;double weekWage = employee.WeeklyWage;if (workTime > 40){weekWage = weekWage + (workTime - 40) * 50;}else if (workTime < 40){weekWage = weekWage - (40 - workTime) * 80;if (weekWage < 0){weekWage = 0;}}Console.WriteLine("正式员工 {0} 实际工资为:{1} 元", employee.Name,  weekWage);}}/// <summary>/// 具体访问者类:HRDepartment/// </summary>public class HRDepartment : Department{// 实现人力资源部对兼职员工数据的访问public override void Visit(PartTimeEmployee employee){int workTime = employee.WorkTime;Console.WriteLine("临时工 {0} 实际工作时间为:{1} 小时", employee.Name, workTime);}// 实现人力资源部对全职员工数据的访问public override void Visit(FullTimeEmployee employee){int workTime = employee.WorkTime;Console.WriteLine("正式员工 {0} 实际工作时间为:{1} 小时", employee.Name, workTime);if (workTime > 40){Console.WriteLine("正式员工 {0} 加班时间为:{1} 小时", employee.Name, workTime - 40);}else if (workTime < 40){Console.WriteLine("正式员工 {0} 请假时间为:{1} 小时", employee.Name, 40 - workTime);}}}

  (6)客户端调用与测试

    public class Program{public static void Main(string[] args){EmployeeList empList = new EmployeeList();IEmployee fteA = new FullTimeEmployee("梁思成", 3200.00, 45);IEmployee fteB = new FullTimeEmployee("徐志摩", 2000, 40);IEmployee fteC = new FullTimeEmployee("梁徽因", 2400, 38);IEmployee fteD = new PartTimeEmployee("方鸿渐", 80, 20);IEmployee fteE = new PartTimeEmployee("唐宛如", 60, 18);empList.AddEmployee(fteA);empList.AddEmployee(fteB);empList.AddEmployee(fteC);empList.AddEmployee(fteD);empList.AddEmployee(fteE);Department dept = AppConfigHelper.GetDeptInstance() as Department;if (dept != null){empList.Accept(dept);}Console.ReadKey();}}

  其中,AppConfigHelper用于从配置文件中获得具体访问者实例,配置文件如下:

<?xml version="1.0" encoding="utf-8" ?>
<configuration><appSettings><add key="DeptName" value="Manulife.ChengDu.DesignPattern.Visitor.HRDepartment, Manulife.ChengDu.DesignPattern.Visitor" /></appSettings>
</configuration>

  AppConfigHelper的具体代码如下:

    public class AppConfigHelper{public static string GetDeptName(){string factoryName = null;try{factoryName = System.Configuration.ConfigurationManager.AppSettings["DeptName"];}catch (Exception ex){Console.WriteLine(ex.Message);}return factoryName;}public static object GetDeptInstance(){string assemblyName = AppConfigHelper.GetDeptName();Type type = Type.GetType(assemblyName);var instance = Activator.CreateInstance(type);return instance;}}

View Code

  编译运行后的结果如下:

  

  如果需要更换具体访问者类,无须修改源代码,只需要修改一下配置文件。例如这里将访问者由人力资源部更改为财务部:

<?xml version="1.0" encoding="utf-8" ?>
<configuration><appSettings><add key="DeptName" value="Manulife.ChengDu.DesignPattern.Visitor.FinanceDepartment, Manulife.ChengDu.DesignPattern.Visitor" /></appSettings>
</configuration>

  此时再次运行则会得到以下结果:

  

  可以看出,如果我们要在系统中新增访问者,那么无需修改源代码,只需新增一个新的具体访问者类即可,从这一点看,访问者模式符合开闭原则。

  但是,如果我们要在系统中新增具体元素,比如新增一个新的员工类型为“退休人员”,由于原系统并未提供相应的访问接口,因此必须对原有系统进行修改。所以,从新增新的元素来看,访问者模式违背了开闭原则

  因此,访问者模式与抽象工厂模式类似,对于开闭原则的支持具有“倾斜”性,可以方便地新增访问者,但是添加新的元素较为麻烦。

四、访问者模式总结

4.1 主要优点

  (1)增加新的访问操作十分方便,不痛不痒 => 符合开闭原则

  (2)将有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个的元素类中,类的职责更加清晰 => 符合单一职责原则

4.2 主要缺点

  (1)增加新的元素类很困难,需要在每一个访问者类中增加相应访问操作代码 => 违背了开闭原则

  (2)元素对象有时候必须暴露一些自己的内部操作和状态,否则无法供访问者访问 => 破坏了元素的封装性

4.3 应用场景

  (1)一个对象结构包含多个类型的对象,希望对这些对象实施一些依赖其具体类型的操作。=> 不同的类型可以有不同的访问操作

  (2)对象结构中对象对应的类很少改变 很少改变 很少改变(重要的事情说三遍),但经常需要在此对象结构上定义新的操作。

参考资料

  

  刘伟,《设计模式的艺术—软件开发人员内功修炼之道》

转载于:https://www.cnblogs.com/Renyi-Fan/p/11105295.html

北风设计模式课程---访问者模式(Visitor)相关推荐

  1. 北风设计模式课程---创建模式、结构模式、行为模式的区别

    北风设计模式课程---创建模式.结构模式.行为模式的区别 一.总结 一句话总结: 创建模式:[关注对象的创建]:创建型模式,就是创建对象的模式,抽象了实例化的过程. 结构模式:结构型模式是为解决[怎样 ...

  2. 北风设计模式课程---解释器模式(Interpreter Pattern)

    北风设计模式课程---解释器模式(Interpreter Pattern) 一.总结 一句话总结: 不仅要通过视频学,还要看别的博客里面的介绍,搜讲解,搜作用,搜实例 设计模式都是对生活的抽象,比如用 ...

  3. 二十四种设计模式:访问者模式(Visitor Pattern)

    访问者模式(Visitor Pattern) 介绍 表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作. 示例 有一个Message实体类,某些对 ...

  4. 设计模式之访问者模式(Visitor)

    访问者模式(Visitor) 在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法.通过这种方式,元素的执行算法可以随着访问者改变而改变 主要将数据结构与 ...

  5. 设计模式之访问者模式(Visitor)摘录

    23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式抽象了实例化过程,它们帮助一个系统独立于如何创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而 ...

  6. 设计模式(17) 访问者模式(VISITOR) C++实现

    意图: 表示一个作用于某对象结构的各元素的操作.它使你可以再不改变各元素的类的前提下定义作用于这些元素的新操作. 动机: 之前在学校的最后一个小项目就是做一个编译器,当时使用的就是访问者模式. 在静态 ...

  7. [设计模式] 23 访问者模式 visitor Pattern

    在GOF的<设计模式:可复用面向对象软件的基础>一书中对访问者模式是这样说的:表示一个作用于某对象结构中的各元素的操作.它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作.访问 ...

  8. 【设计模式】—— 访问者模式Visitor

    对于某个对象或者一组对象,不同的访问者,产生的结果不同,执行操作也不同.此时,就是访问者模式的典型应用了. 应用场景 1 不同的子类,依赖于不同的其他对象 2 需要对一组对象,进行许多不相关的操作,又 ...

  9. 设计模式:访问者(Visitor)模式

    设计模式:访问者(Visitor)模式 一.前言    什么叫做访问,如果大家学过数据结构,对于这点就很清晰了,遍历就是访问的一般形式,单独读取一个元素进行相应的处理也叫作访问,读取到想要查看的内容+ ...

最新文章

  1. CSS grid 的用法
  2. 下一跳网关和转发接口的使用情况
  3. Sendmail+dovecot+saslauth+rainloop
  4. Java中的几种引用类型_Java中的几种引用类型(转载)
  5. “进度条”博客——第十六周
  6. [Qt教程] 第15篇 2D绘图(五)绘制图片
  7. 数据结构(七)---循环队列的实现---java版
  8. Turtlebot3调试必看——爬坑笔记
  9. 常量与变量的区别(详细说明)(学习笔记3--变量与常量)
  10. 关于SVN出现 svn working copy locked的原因及解决方法
  11. 数字信号的增益matlab,如何控制多频带滤波器通带增益?
  12. 麦克斯韦方程组(彩图完美解释版)
  13. BATH四巨头鼎立,中国云市场将走向何方?
  14. LTE 中的ANR以及TNL介绍
  15. OPencv无损保存图片
  16. css动画 翻开折叠生日贺卡
  17. 正负数值的正则表达式
  18. TCP连接建立三握手
  19. Android 模拟返回键、菜单键、主页键
  20. 核心微生物分析_微生物总结(全)

热门文章

  1. 树莓派python智能家居英文参考文献_Home Assistant + 树莓派:强大的智能家居系统 · 设备接入篇...
  2. 将一个大数转为科学计数法并保留两位小数打印
  3. DT时代下数据安全运营面临的主要挑战
  4. android打印doc文件,安卓系统APP打印开发实例.doc
  5. JS运算符% 和 /
  6. Bugku WEB decrypt
  7. halcon算子表参考
  8. 锁相环技术,单边带信号,信号的调制
  9. [Python 知识点]类的学习 做个儿时的小游戏
  10. mysql 左外连接原理_深入理解SQL的四种连接-左外连接、右外连接、内连接、全连接...