访问者模式 (Visitor Pattern)【使用频率:★☆☆☆☆】
1. 概述:
提供一个作用于某对象结构中的各元素的操作,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
2. 模式中的角色
2.1 Vistor(抽象访问者):抽象访问者为对象结构中每一个具体元素类ConcreteElement声明一个访问操作,从这个操作的名称或参数类型可以清楚知道需要访问的具体元素的类型,具体访问者需要实现这些操作方法,定义对这些元素的访问操作。
2.2 ConcreteVisitor(具体访问者):具体访问者实现了每个由抽象访问者声明的操作,每一个操作用于访问对象结构中一种类型的元素。
2.3 Element(抽象元素):抽象元素一般是抽象类或者接口,它定义一个accept()方法,该方法通常以一个抽象访问者作为参数。
2.4 ConcreteElement(具体元素):具体元素实现了accept()方法,在accept()方法中调用访问者的访问方法以便完成对一个元素的操作。
2.5 ObjectStructure(对象结构):对象结构是一个元素的集合,它用于存放元素对象,并且提供了遍历其内部元素的方法。它可以结合组合模式来实现,也可以是一个简单的集合对象,如一个List对象或一个Set对象。
3. 模式解读
3.1 模式的类图
3.2 代码实现
以在医院医生开药、药房取药为实例。在这个实例中医生和药房工作者作为访问者,药品作为访问元素、处方单作为对象结构
using System;
using System.Collections.Generic;
namespace ConsoleApp2
{
class Class29
{
public static void Main(string[] args)
{
Medicine a = new MedicineA("板蓝根", 11.0);
Medicine b = new MedicineB("感康", 14.3);
// 处方单
Prescription presciption = new Prescription();
presciption.AddMedicine(a);
presciption.AddMedicine(b);
// 医生
Visitor doctor = new Doctor();
doctor.setName("张三");
// 药房拿药者
Visitor workerOfPharmacy = new WorkerOfPharmacy();
workerOfPharmacy.setName("李四");
presciption.Accept(doctor);
Console.WriteLine("------------------------------");
presciption.Accept(workerOfPharmacy);
Console.ReadLine();
}
}
// 抽象访问者
public abstract class Visitor
{
protected string name;
public void setName(string name)
{
this.name = name;
}
public abstract void visitor(MedicineA a);
public abstract void visitor(MedicineB b);
}
// 具体访问者:医生
public class Doctor : Visitor
{
public override void visitor(MedicineA a)
{
Console.WriteLine("医生:" + name + ",开药:" + a.getName() + ",价格:" + a.getPrice());
}
public override void visitor(MedicineB b)
{
Console.WriteLine("医生:" + name + ",开药:" + b.getName() + ",价格:" + b.getPrice());
}
}
// 具体访问者:药房工作者
public class WorkerOfPharmacy : Visitor
{
public override void visitor(MedicineA a)
{
Console.WriteLine("药房工作者:" + name + ",拿药:" + a.getName());
}
public override void visitor(MedicineB b)
{
Console.WriteLine("药房工作者:" + name + ",拿药:" + b.getName());
}
}
// 抽象元素:药品
public abstract class Medicine
{
protected string name;
protected double price;
public Medicine(string name, double price)
{
this.name = name;
this.price = price;
}
public string getName()
{
return name;
}
public void setName(string name)
{
this.name = name;
}
public double getPrice()
{
return price;
}
public void setPrice(double price)
{
this.price = price;
}
public abstract void Accept(Visitor visitor);
}
// 具体元素:药品A
public class MedicineA : Medicine
{
public MedicineA(string name, double price) : base(name, price)
{
}
public override void Accept(Visitor visitor)
{
visitor.visitor(this);
}
}
// 具体元素:药品B
public class MedicineB : Medicine
{
public MedicineB(string name, double price) : base(name, price)
{
}
public override void Accept(Visitor visitor)
{
visitor.visitor(this);
}
}
// 对象结构:处方单
// 用于存放元素对象,并且提供了遍历其内部元素的方法。
public class Prescription
{
List<Medicine> list = new List<Medicine>();
public void Accept(Visitor visitor)
{
for (int i = 0; i < list.Count; i++)
{
list[i].Accept(visitor);
}
}
public void AddMedicine(Medicine medicine)
{
list.Add(medicine);
}
public void RemoveMedicien(Medicine medicine)
{
list.Remove(medicine);
}
}
}
输出结果:
4. 模式优缺点
4.1 优点:
(1) 增加新的访问操作很方便。使用访问者模式,增加新的访问操作就意味着增加一个新的具体访问者类,实现简单,无须修改源代码,符合“开闭原则”。
(2) 将有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个的元素类中。类的职责更加清晰,有利于对象结构中元素对象的复用,相同的对象结构可以供多个不同的访问者访问。
(3) 让用户能够在不修改现有元素类层次结构的情况下,定义作用于该层次结构的操作。
4.2 缺点:
(1) 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”的要求。
(2) 破坏封装。访问者模式要求访问者对象访问并调用每一个元素对象的操作,这意味着元素对象有时候必须暴露一些自己的内部操作和内部状态,否则无法供访问者访问。
5. 使用场景:
(1) 一个对象结构包含多个类型的对象,希望对这些对象实施一些依赖其具体类型的操作。在访问者中针对每一种具体的类型都提供了一个访问操作,不同类型的对象可以有不同的访问操作。
(2) 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。访问者模式使得我们可以将相关的访问操作集中起来定义在访问者类中,对象结构可以被多个不同的访问者类所使用,将对象本身与对象的访问操作分离。
(3) 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。