访问者模式
文章目录
前言
在生活中,针对同一件物品,因为观察角度不同,所得到的结果也有所不同,访问者以某一类的物品触发,详细实现了多角度多物品的观察结果,比如规则性物体,可以抽象出来连个元素,正方体和长方体。抽象访问者可以抽象出来观察的角度,例如正视图,左视图,俯视图,然后由具体访问者实现观察角度,子类与实际元素类数量相同,可以使不同的物体具有相同的观察操作,通过结构对象维护元素集合,提供方法接受访问者对元素进行操作。
一、访问者模式(Visitor)
可以将数据结构与数据操作分离,比如封装一些操作某种数据结构的算法,可以在不改变数据结构的前提下,定义元素的新操作。
二、角色
- 抽象访问者(IVisitor):接口或抽象类,定义了一个visit方法用于访问每个具体的元素,参数时具体对象。
- 具体访问者(Concret Visitor):实现对具体结构对象的元素的操作。
- 抽象元素(IElement):接口或抽象类,定义了一个接受访问者访问的方法accept(),表示所有元素类型都支持被访问者访问。
- 具体元素(Concrete Element): 接受访问者的具体实现。
- 结构对象(Object Structure):维护元素集合,并提供方法接受访问者对该集合所有元素进行操作。
三、应用场景
- 数据结构稳定,数据结构操作经常变化的场景
- 数据结构与数据操作分离的场景
- 需要对不同数据类型(元素)进行操作,而不使用分支判断具体类型的场景。
四、代码实现
1. 抽象元素
代码如下(示例): 抽象被访问者,定义某类元素的抽象
/// <summary>
/// 抽象元素
/// 抽象被访问者
///
/// </summary>
public interface IEmployee
{
public string Name { get; set; }
public string Depament { get; set; }
public int KPI { get; set; }
void Accept(IKPIVisitor kPIVisitor);
}
2.具体元素
代码如下(示例):被访问的元素以及被访问的方法
/// <summary>
/// 被访问的元素以及被访问的方法
/// </summary>
public class Dev : IEmployee
{
public Dev(string Name)
{
this.Name = Name;
this.Depament = "开发";
this.KPI = new Random().Next(10);
}
public int KPI { get; set; }
public string Name { get; set; }
public string Depament { get; set; }
public void Accept(IKPIVisitor kPIVisitor)
{
kPIVisitor.KPIVisitor(this);
}
public int CodeRowCount()
{
var bugNumber = new Random().Next(100000);
Console.WriteLine($"部门{this.Depament},{this.Name}写出{bugNumber}行代码");
return bugNumber;
}
}
/// <summary>
/// 被访问的元素以及被访问的方法
/// </summary>
public class ProjectManager : IEmployee
{
public ProjectManager(string Name)
{
this.Name = Name;
this.Depament = "PM";
this.KPI = new Random().Next(10);
}
public int KPI { get; set; }
public string Name { get; set; }
public string Depament { get; set; }
public void Accept(IKPIVisitor kPIVisitor)
{
kPIVisitor.KPIVisitor(this);
}
public int DomandaEffettiva()
{
var bugNumber = new Random().Next(10000);
Console.WriteLine($"部门{this.Depament},{this.Name}提出{bugNumber}个有效需求");
return bugNumber;
}
}
/// <summary>
/// 被访问的元素以及被访问的方法
/// </summary>
public class Tester : IEmployee
{
public Tester(string Name)
{
this.Name = Name;
this.Depament = "测试";
this.KPI = new Random().Next(10);
}
public int KPI { get; set; }
public string Name { get; set; }
public string Depament { get; set; }
public void Accept(IKPIVisitor kPIVisitor)
{
kPIVisitor.KPIVisitor(this);
}
public int BugNumber()
{
var bugNumber = new Random().Next(10000);
Console.WriteLine($"部门{this.Depament},{this.Name}测试出BUG{bugNumber}个");
return bugNumber;
}
}
3.定义提供给访问者的抽象方法
代码如下(示例):所有具体访问者都继承自这个抽象,然后在继承者内进行内部实现,实现子类内部关注内容,比如,技术总监关注具体的工作内容,CEO关注任务的完成度
/// <summary>
/// 定义提供给访问者的抽象方法
/// 所有具体访问者都继承自这个抽象,然后在继承者内进行内部实现,实现子类内部关注内容
/// 比如,技术总监关注具体的工作内容,CEO关注任务的完成度
/// </summary>
public interface IKPIVisitor
{
/// <summary>
/// 开发的KPI实现
/// </summary>
/// <param name="employee"></param>
void KPIVisitor(Dev employee);
/// <summary>
/// 产品的KPI实现
/// </summary>
/// <param name="employee"></param>
void KPIVisitor(ProjectManager employee);
/// <summary>
/// 测试的KPI实现
/// </summary>
/// <param name="employee"></param>
void KPIVisitor(Tester employee);
}
4.CEO关注KPI
代码如下(示例):
/// <summary>
/// CEO关注KPI
/// </summary>
public class CEOVisitor : IKPIVisitor
{
public void KPIVisitor(Dev employee)
{
Console.WriteLine($"{employee.Depament}:{employee.Name},KPI为{employee.KPI}");
}
public void KPIVisitor(ProjectManager employee)
{
Console.WriteLine($"{employee.Depament}:{employee.Name},KPI为{employee.KPI}");
}
public void KPIVisitor(Tester employee)
{
Console.WriteLine($"{employee.Depament}:{employee.Name},KPI为{employee.KPI}");
}
}
/// <summary>
/// CTo关注的所有内容实现
/// </summary>
public class CTOVisitor : IKPIVisitor
{
/// <summary>
/// CTO关注开发的代码行数
/// </summary>
/// <param name="employee"></param>
public void KPIVisitor(Dev employee)
{
Console.WriteLine($"{employee.Depament}:{employee.Name},代码行数为{employee.CodeRowCount()}");
}
/// <summary>
/// CTO关注产品的有效需求
/// </summary>
/// <param name="employee"></param>
public void KPIVisitor(ProjectManager employee)
{
Console.WriteLine($"{employee.Depament}:{employee.Name},有效需求{employee.DomandaEffettiva()}");
}
/// <summary>
/// CTO关注测试的BUG数
/// </summary>
/// <param name="employee"></param>
/// <exception cref="NotImplementedException"></exception>
public void KPIVisitor(Tester employee)
{
Console.WriteLine($"{employee.Depament}:{employee.Name},BUG数{employee.BugNumber()}");
}
}
5.结构化对象
代码如下(示例):结构化对象
/// <summary>
/// 结构化对象
/// </summary>
public class BussingssReposrt
{
private List<IEmployee> employees = new List<IEmployee>();
public BussingssReposrt()
{
employees.Add(new Dev("开发菜鸟"));
employees.Add(new Dev("开发菜鸡"));
employees.Add(new ProjectManager("产品菜鸟"));
employees.Add(new ProjectManager("产品菜鸡"));
employees.Add(new Tester("测试大牛"));
}
public void showReport(IKPIVisitor kPIVisitor)
{
foreach (var item in employees)
{
item.Accept(kPIVisitor);
}
}
}
6.访问客户端
代码如下(示例):结构化对象
public class EmployeeClient {
public void clientMain() {
BussingssReposrt bussingssReposrt = new BussingssReposrt();
Console.WriteLine("----------------------------CEO------------------------------");
bussingssReposrt.showReport(new CEOVisitor());
Console.WriteLine("----------------------------CTO------------------------------");
bussingssReposrt.showReport(new CTOVisitor());
}
}
7.代码输出
代码如下(示例):
----------------------------CEO------------------------------
开发:开发菜鸟,KPI为3
开发:开发菜鸡,KPI为7
PM:产品菜鸟,KPI为5
PM:产品菜鸡,KPI为0
测试:测试大牛,KPI为4
----------------------------CTO------------------------------
部门开发,开发菜鸟写出17530行代码
开发:开发菜鸟,代码行数为17530
部门开发,开发菜鸡写出75555行代码
开发:开发菜鸡,代码行数为75555
部门PM,产品菜鸟提出2673个有效需求
PM:产品菜鸟,有效需求2673
部门PM,产品菜鸡提出1719个有效需求
PM:产品菜鸡,有效需求1719
部门测试,测试大牛测试出BUG9341个
测试:测试大牛,BUG数9341
总结
相同的数据结构,观察角度不同,生成的报表内容不同。
优点:
- 数据结构与数据操作解耦,使操作集合可以独立变化
- 扩展访问者角色,实现对数据集群的不同操作,程序扩展性更高
- 元素具体类型并非单一,访问者均可操作
- 各角色职责分离,符合单一职责原则。
缺点:
- 无法增加元素类型
- 具体元素变更困难
- 违背了依赖倒置原则,