设计模式(二十三)访问者

一、定义

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

二、描述

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

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

三、例子

X公司开发部想要为某企业开发一个OA系统,员工管理模块分为正式员工和临时工,HR部门和财务部门每周按各自计算公式分别汇总员工对应工时、工资

IEmployee:员工接口,充当抽象元素

public interface IEmployee
{
    void Accept(Department handler);
}

FullTimeEmployee,PartTimeEmployee:正式员工类、临时员工类,充当具体元素

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);
    }
}

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);
    }
}

EmployeeList:员工集合类,充当对象结构

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);
        }
    }
}

Department:部门抽象类,充当抽象访问者

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

FinanceDepartment,HRDepartment:财务部门类、人力部门类,充当具体访问者

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);
    }
}

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);
        }
    }
}

Program:测试代码

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 = new HRDepartment();
if (dept != null)
{
    empList.Accept(dept);
}
Console.ReadLine();

在系统中新增访问者,那么无需修改源代码,只需新增一个新的具体访问者类即可,符合开闭原则,但是,如果要新增具体元素,比如新增一个新的员工类型为“退休人员”,由于原系统并未提供相应的访问接口,因此必须对原有系统进行修改。所以,从新增新的元素来看,访问者模式违背了开闭原则。访问者模式与抽象工厂模式类似,对于开闭原则的支持具有“倾斜”性,可以方便地新增访问者,但是添加新的元素较为麻烦。

四、总结

1、优点

(1)访问者模式增加新的访问操作很方便。使用访问者模式,增加新的访问操作就意味着增加一个新的具体访问者类,实现简单,无须修改源代码,符合开闭原则。
(2)访问者模式将有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个的元素类中。类的职责更加清晰,有利于对象结构中元素对象的复用,相同的对象结构可以供多个不同的访问者访问。
(3)访问者模式让用户能够在不修改现有元素类层次结构的情况下,定义作用于该层次结构的操作。

2、缺点

(1)增加新的元素类很困难。在访问者模式中,每增加一个新的元素类都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作,这违背了开闭原则的要求。
(2)破坏封装。访问者模式要求访问者对象访问并调用每一个元素对象的操作,这意味着元素对象有时候必须暴露一些自己的内部操作和内部状态,否则无法供访问者访问。

热门相关:诱抱:床战   延边女士风味季   梦蝶   朋友的女朋友   女神异闻录5 动画版 年末特别篇前篇