一、定義 表示一個作用於某對象結構中的各個元素的操作。訪問者模式讓你可以在不改變各元素的類的前提下定義作用於這些元素的新操作。訪問者模式是一種對象行為型模式 二、描述 訪問者模式是一種較為複雜的行為型模式,它包含訪問者和被訪問元素兩個主要組成部分,這些被訪問的元素通常具有不同的類型,且不同的訪問者可 ...
一、定義
表示一個作用於某對象結構中的各個元素的操作。訪問者模式讓你可以在不改變各元素的類的前提下定義作用於這些元素的新操作。訪問者模式是一種對象行為型模式
二、描述
訪問者模式是一種較為複雜的行為型模式,它包含訪問者和被訪問元素兩個主要組成部分,這些被訪問的元素通常具有不同的類型,且不同的訪問者可以對它們進行不同的訪問操作。例如:處方單中的各種藥品信息就是被訪問的元素,而劃價人員和藥房工作人員就是訪問者。訪問者模式可以使得用戶在不修改現有系統的情況下擴展系統的功能,為這些不同類型的元素增加新的操作,包含以下五個角色: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)破壞封裝。訪問者模式要求訪問者對象訪問並調用每一個元素對象的操作,這意味著元素對象有時候必須暴露一些自己的內部操作和內部狀態,否則無法供訪問者訪問。