专业的编程技术博客社区

网站首页 > 博客文章 正文

设计模式(29)--行为型模式_访问者模式Visitor

baijin 2024-10-01 07:31:39 博客文章 12 ℃ 0 评论

0、参考

(1)视频:https://www.bilibili.com/video/BV1Jz4y1d7TX/?p=24

(2)比较好:https://zhuanlan.zhihu.com/p/346511627

1、主要概念

1.1类层次的结构变化

类层次结构中可能经常由于引入新的操作,从而将类型变得脆弱...

1.2动机

在软件构建过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为(方法)(特别是虚方法),如果直接在基类中做这样的更改,将会给子类带来很繁重的变更负担,甚至破坏原有设计。

如何在不更改类层次结构的前提下,在运行时根据需要透明地为类层次结构上的各个类动态添加新的操作,从而避免上述问题?

1.3意图

表示一个作用于某对象结构中的各元素的操作。它可以在不改变各元素的类的前提下定义作用于这些元素的新的操作。(《设计模式》GoF)

2、结构图

2.1结构图

提供一个作用于某对象结构中的各元素的操作,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

2.2 模式中的角色

  • (1)Vistor(抽象访问者):抽象访问者为对象结构中每一个具体元素类ConcreteElement声明一个访问操作,从这个操作的名称或参数类型可以清楚知道需要访问的具体元素的类型,具体访问者需要实现这些操作方法,定义对这些元素的访问操作。
  • (2) ConcreteVisitor(具体访问者):具体访问者实现了每个由抽象访问者声明的操作,每一个操作用于访问对象结构中一种类型的元素。
  • (3) Element(抽象元素):抽象元素一般是抽象类或者接口,它定义一个accept()方法,该方法通常以一个抽象访问者作为参数。
  • (4) ConcreteElement(具体元素):具体元素实现了accept()方法,在accept()方法中调用访问者的访问方法以便完成对一个元素的操作。
  • (5)ObjectStructure(对象结构):对象结构是一个元素的集合,它用于存放元素对象,并且提供了遍历其内部元素的方法。它可以结合组合模式来实现,也可以是一个简单的集合对象,如一个List对象或一个Set对象。

3、测试例子

主要参考了:https://zhuanlan.zhihu.com/p/346511627

以在医院医生开药、药房取药为实例。在这个实例中医生和药房工作者作为访问者,药品作为访问元素、处方单作为对象结构

3.1 各模块定义

(1) 抽象访问者

注意1:定义了2个visitor抽象方法,而且这两个visitor方法是重载的(参数类型不同),

//(1) 抽象访问者
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);
}

(2)定义2个具体访问者1:医生和药房工作者,

注意2:重写了2个visitor方法,而且这两个visitor方法是重载的(参数类型不同),

//(2.1)定义具体访问者1:医生
public class Doctor : Visitor
{
    public override void visitor(MedicineA a)
    {
        Console.WriteLine("医生:" + name + ",开药:" + a.Name + ",价格:" + a.Price);
    }

    public override void visitor(MedicineB b)
    {
        Console.WriteLine("医生:" + name + ",开药:" + b.Name + ",价格:" + b.Price);
    }
}
//(2.2)定义具体访问者2:药房工作者
public class WorkerOfPharmacy : Visitor
{
    public override void visitor(MedicineA a)
    {
        Console.WriteLine("药房工作者:" + name + ",拿药:" + a.Name);
    }
    public override void visitor(MedicineB b)
    {
        Console.WriteLine("药房工作者:" + name + ",拿药:" + b.Name);
    }
}

(3)定义抽象元素:药品

注意3:定义了一个抽象方法Accept,参数为Visitor类型

//(3) 定义抽象元素:药品
public abstract class Medicine
{
    public string Name { set; get; }
    public double Price { set; get; }
    public Medicine(string name, double price)
    {
        this.Name = name;
        this.Price = price;
    }
    public abstract void Accept(Visitor visitor);
}

(4)定义了2个具体元素(实际应用中会有好多个),药品A和药品B

注意4:visitor.visitor(this)方法,其中的this将现在自己的类如:MedicineA传参了过去。

//(4.1) 定义了具体元素1(实际应用中会有好多个):药品A
public class MedicineA : Medicine
{
    public MedicineA(string name, double price) : base(name, price)
    {
    }
    public override void Accept(Visitor visitor)
    {
        visitor.visitor(this);
    }
}
//(4.2) 定义了具体元素2(实际应用中会有好多个):药品B
public class MedicineB : Medicine
{
    public MedicineB(string name, double price) : base(name, price)
    {
    }
    public override void Accept(Visitor visitor)
    {
        visitor.visitor(this);
    }
}

(5)对象结构:处方单。用于存放元素对象,并且提供了遍历其内部元素的方法。

// (5)对象结构:处方单。用于存放元素对象,并且提供了遍历其内部元素的方法。
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);
    }
}

3.2完成代码实现与测试

(1)完整代码

//(1) 抽象访问者
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);
}
//(2.1)定义具体访问者1:医生
public class Doctor : Visitor
{
    public override void visitor(MedicineA a)
    {
        Console.WriteLine("医生:" + name + ",开药:" + a.Name + ",价格:" + a.Price);
    }

    public override void visitor(MedicineB b)
    {
        Console.WriteLine("医生:" + name + ",开药:" + b.Name + ",价格:" + b.Price);
    }
}
//(2.2)定义具体访问者2:药房工作者
public class WorkerOfPharmacy : Visitor
{
    public override void visitor(MedicineA a)
    {
        Console.WriteLine("药房工作者:" + name + ",拿药:" + a.Name);
    }
    public override void visitor(MedicineB b)
    {
        Console.WriteLine("药房工作者:" + name + ",拿药:" + b.Name);
    }
}

//(3) 定义抽象元素:药品
public abstract class Medicine
{
    public string Name { set; get; }
    public double Price { set; get; }
    public Medicine(string name, double price)
    {
        this.Name = name;
        this.Price = price;
    }
    public abstract void Accept(Visitor visitor);
}
//(4.1) 定义了具体元素1(实际应用中会有好多个):药品A
public class MedicineA : Medicine
{
    public MedicineA(string name, double price) : base(name, price)
    {
    }
    public override void Accept(Visitor visitor)
    {
        visitor.visitor(this);
    }
}
//(4.2) 定义了具体元素2(实际应用中会有好多个):药品B
public class MedicineB : Medicine
{
    public MedicineB(string name, double price) : base(name, price)
    {
    }
    public override void Accept(Visitor visitor)
    {
        visitor.visitor(this);
    }
}
// (5)对象结构:处方单。用于存放元素对象,并且提供了遍历其内部元素的方法。
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);
    }
}

class Demo
{
    public static void Main(string[] args)
    {
        Medicine a = new MedicineA("板蓝根", 16.0);
        Medicine b = new MedicineB("感康", 18.2);
        // 处方单
        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);
    }
}

(2)测试结果:

3.3扩展说明

如果要增加新的访问操作,就只要增加一个新的具体访问者类,然后重写特定的visitor(XXX)方法即可。

4. 访问者模式的优缺点

4.1优点

(1)增加新的访问操作很方便。使用访问者模式,增加新的访问操作就意味着增加一个新的具体访问者类,实现简单,无须修改源代码,符合“开闭原则”。

(2)将有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个的元素类中。类的职责更加清晰,有利于对象结构中元素对象的复用,相同的对象结构可以供多个不同的访问者访问。

(3)让用户能够在不修改现有元素类层次结构的情况下,定义作用于该层次结构的操作。

4.2缺点

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

5、Visitor模式的几个要点

  • Visitor模式通过所谓双重分发(double dispatch)来实现在不更改Element类层次结构的前提下,在运行时透明地为类层次结构上的各个类动态添加新的操作。
  • 所谓双重分发即Visitor模式中间包括了两个多态分发(注意其中的多态机制):第一个为accept方法的多态辨析;第二个为visit方法的多态辨析。
  • Visitor模式的最大缺点在于扩展类层次结构(增添新的Element子类),会导致Visitor类的改变。因此Visitor模式适用于“Element类层次结构稳定,而其中的操作却经常面临频繁改动”。

6使用场景

  • (1) 一个对象结构包含多个类型的对象,希望对这些对象实施一些依赖其具体类型的操作。在访问者中针对每一种具体的类型都提供了一个访问操作,不同类型的对象可以有不同的访问操作。
  • (2) 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作“污染”这些对象的类,也不希望在增加新操作时修改这些类。访问者模式使得我们可以将相关的访问操作集中起来定义在访问者类中,对象结构可以被多个不同的访问者类所使用,将对象本身与对象的访问操作分离。
  • (3) 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表