設計模式(二十三)訪問者

冬先生發表於2024-01-29

一、定義

表示一個作用於某物件結構中的各個元素的操作。訪問者模式讓你可以在不改變各元素的類的前提下定義作用於這些元素的新操作。訪問者模式是一種物件行為型模式

二、描述

訪問者模式是一種較為複雜的行為型模式,它包含訪問者和被訪問元素兩個主要組成部分,這些被訪問的元素通常具有不同的型別,且不同的訪問者可以對它們進行不同的訪問操作。例如:處方單中的各種藥品資訊就是被訪問的元素,而劃價人員和藥房工作人員就是訪問者。訪問者模式可以使得使用者在不修改現有系統的情況下擴充套件系統的功能,為這些不同型別的元素增加新的操作,包含以下五個角色: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)破壞封裝。訪問者模式要求訪問者物件訪問並呼叫每一個元素物件的操作,這意味著元素物件有時候必須暴露一些自己的內部操作和內部狀態,否則無法供訪問者訪問。

相關文章