11.访问者模式

11.行为型模式-访问者模式


11.1 基础知识

学习难度:4

使用频率:1

总分:3

定义

访问者模式(Visitor Pattern)表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

说人话

这种模式就是让你可以对一个对象结构中的各种元素进行操作,而不需要修改这些元素的类。你可以定义新的操作,然后让一个访问者对象来执行这些操作。

结构图

实现步骤

  • 元素接口:定义了接受访问者的方法,允许访问者访问元素。
  • 具体元素类:实现元素接口,实现接受访问者的方法。在方法中调用访问者对象的访问当前具体元素的方法,传入自己作为参数。定义具体元素的行为和数据。
  • 访问者接口:定义多个访问不同具体元素的方法。每个方法对应一个具体元素对象作为参数。
  • 具体访问者类:实现访问者接口,实现访问具体元素的方法。通常会调用具体元素中的行为和数据。
  • 对象结构类:定义元素容器,提供添加和移除元素的方法。提供访问方法接收访问者接口对象,将访问分发给各个元素。
  • 客户端:实例化对象结构,实例化一些具体元素并添加到对象结构中。实例化具体访问者,然后让对象结构接收访问者。

说明

访问者模式适用于对象结构比较稳定,但经常需要在此结构上定义新的操作的情况。


11.2 模版代码

元素接口和具体元素类

// 元素接口
interface IElement
{
    // 接受访问者的方法
    void Accept(IVisitor visitor);
}

// 具体元素类A
class ConcreteElementA : IElement
{
    public void Accept(IVisitor visitor)
    {
        // 具体元素A接受访问者的访问,并调用访问者的方法
        visitor.VisitConcreteElementA(this);
    }

    public void OperationA()
    {
        Console.WriteLine("具体元素A的操作A被调用");
    }
}

// 具体元素类B
class ConcreteElementB : IElement
{
    public void Accept(IVisitor visitor)
    {
        // 具体元素B接受访问者的访问,并调用访问者的方法
        visitor.VisitConcreteElementB(this);
    }

    public void OperationB()
    {
        Console.WriteLine("具体元素B的操作B被调用");
    }
}

访问者接口和具体访问者类

// 访问者接口
interface IVisitor
{
    // 访问具体元素A的方法
    void VisitConcreteElementA(ConcreteElementA element);

    // 访问具体元素B的方法
    void VisitConcreteElementB(ConcreteElementB element);
}

// 具体访问者类
class ConcreteVisitor : IVisitor
{
    public void VisitConcreteElementA(ConcreteElementA element)
    {
        Console.WriteLine("具体访问者访问具体元素A");
        element.OperationA();
    }

    public void VisitConcreteElementB(ConcreteElementB element)
    {
        Console.WriteLine("具体访问者访问具体元素B");
        element.OperationB();
    }
}

对象结构类

// 对象结构类
class ObjectStructure
{
    private List<IElement> elements = new List<IElement>();

    // 添加元素到对象结构
    public void Attach(IElement element)
    {
        elements.Add(element);
    }

    // 从对象结构中移除元素
    public void Detach(IElement element)
    {
        elements.Remove(element);
    }

    // 接受访问者的方法,用于遍历所有元素并调用它们的Accept方法
    public void Accept(IVisitor visitor)
    {
        foreach (IElement element in elements)
        {
            element.Accept(visitor);
        }
    }
}

客户端

class Program
{
    static void Main(string[] args)
    {
        // 创建对象结构
        ObjectStructure objectStructure = new ObjectStructure();

        // 向对象结构添加具体元素A和具体元素B
        objectStructure.Attach(new ConcreteElementA());
        objectStructure.Attach(new ConcreteElementB());

        // 创建具体访问者
        ConcreteVisitor visitor = new ConcreteVisitor();

        // 对象结构接受访问者的访问,从而触发访问者的方法
        objectStructure.Accept(visitor);
        // 具体访问者访问具体元素A
        // 具体元素A的操作A被调用
        // 具体访问者访问具体元素B
        // 具体元素B的操作B被调用

    }
}

11.3 CSharp实践

实践需求

使用访问者模式,通过访问者可以对圆形和矩形计算面积和周长。

图形接口和具体图形类

// 图形接口
public interface IShape
{
    // 接受访问者方法
    void Accept(IShapeVisitor shapeVisitor);
}

// 圆形图形类
public class Circle : IShape
{
    public double Radius { get; set; }

    public Circle(double radius)
    {
        Radius = radius;
    }

    public void Accept(IShapeVisitor shapeVisitor)
    {
        // 调用具体访问者的方法以执行操作
        shapeVisitor.VisitCircle(this);
    }
}

// 矩形图形类
public class Rectangle : IShape
{
    public double Width { get; set; }
    public double Height { get; set; }

    public Rectangle(double width, double height)
    {
        Width = width;
        Height = height;
    }

    public void Accept(IShapeVisitor shapeVisitor)
    {
        shapeVisitor.VisitRectangle(this);
    }
}

图形访问者接口和具体图形访问者类

// 图形访问者接口
public interface IShapeVisitor
{
    // 访问圆形的方法
    void VisitCircle(Circle circle);

    // 访问矩形的方法
    void VisitRectangle(Rectangle rectangle);
}

// 计算图形面积的访问者
public class AreaCalculator : IShapeVisitor
{
    public void VisitCircle(Circle circle)
    {
        // 计算圆形的面积
        double totalArea = Math.PI * circle.Radius * circle.Radius;
        Console.WriteLine("圆形的面积是:" + totalArea);
    }

    public void VisitRectangle(Rectangle rectangle)
    {
        // 计算矩形的面积
        double totalArea = rectangle.Width * rectangle.Height;
        Console.WriteLine("矩形的面积是:" + totalArea);
    }
}

// 计算图形周长的访问者
public class PerimeterCalculator : IShapeVisitor
{
    public void VisitCircle(Circle circle)
    {
        // 计算圆形的周长
        double totalPerimeter = 2 * Math.PI * circle.Radius;
        Console.WriteLine("圆形的周长是:" + totalPerimeter);
    }

    public void VisitRectangle(Rectangle rectangle)
    {
        // 计算矩形的周长
        double totalPerimeter = 2 * (rectangle.Width + rectangle.Height);
        Console.WriteLine("矩形的周长是:" + totalPerimeter);
    }
}

客户端

public class Program
{
    public static void Main()
    {
        // 创建包含两个图形对象的列表
        var shapes = new List<IShape>
        {
            new Circle(10), // 创建半径为 10 的圆形对象
            new Rectangle(4, 6) // 创建宽为 4,高为 6 的矩形对象
        };

        // 创建用于计算面积和周长的访问者对象
        IShapeVisitor areaCalculator = new AreaCalculator(); 
        IShapeVisitor perimeterCalculator = new PerimeterCalculator();

        // 使用访问者模式执行操作
        foreach (var shape in shapes)
        {
            shape.Accept(areaCalculator); // 计算该图形的面积
            shape.Accept(perimeterCalculator); // 计算该图形的周长
            // 输出该图形的面积和周长
            // 圆形的面积是:314.1592653589793
            // 圆形的周长是:62.83185307179586
            // 矩形的面积是:24
            // 矩形的周长是:20
        }
    }
}

11.4 Unity实践

实践需求

使用访问者模式,实现薇恩和剑圣释放QW技能,按E查看薇恩和剑圣是否有伤害性技能和治疗型技能。

元素接口

// 元素接口
public interface IElement
{
    // 用于接受访问者对象的方法
    void Accept(IVisitor visitor);
}

英雄元素类和具体英雄元素类

// 英雄元素类
public abstract class Hero : MonoBehaviour, IElement
{
    // 英雄的名字
    public string heroName;

    // 存储技能的字典,键为按键码,值为技能对象
    public Dictionary<KeyCode, Skill> skillDictionary = new Dictionary<KeyCode, Skill>();

    // 抽象方法,用于接受访问者对象
    public abstract void Accept(IVisitor visitor);

    // 使用技能的方法,根据按键码执行相应的技能
    public void UseSkill(KeyCode keyCode)
    {
        if (skillDictionary.ContainsKey(keyCode))
        {
            string keyString = keyCode.ToString();

            // 通过获取游戏对象上的动画控制器,触发指定名称的触发器
            this.gameObject.GetComponent<Animator>().SetTrigger(keyString);

            // 如果技能造成伤害大于0,则接受伤害访问者对象
            if (skillDictionary[keyCode].damage > 0)
            {
                skillDictionary[keyCode].Accept(new DamageVisitor());
            }

            // 如果技能恢复生命大于0,则接受恢复生命访问者对象
            if (skillDictionary[keyCode].heal > 0)
            {
                skillDictionary[keyCode].Accept(new HealVisitor());
            }
        }
        else
        {
            // 如果按键码不在技能字典中,输出警告信息
            Debug.LogWarning(heroName + "不具备技能:" + skillDictionary[keyCode].name);
        }
    }
}

// 薇恩英雄类
public class VayneHero : Hero
{
    // 在开始时设置英雄名字和技能
    void Start()
    {
        heroName = "暗夜猎手薇恩";

        // 创建名为 "tumble" 的技能,并将其添加到技能字典中
        Skill tumble = new Tumble(KeyCode.Q);
        skillDictionary.Add(tumble.keyCode, tumble);

        // 创建名为 "silverBolts" 的技能,并将其添加到技能字典中
        Skill silverBolts = new SilverBolts(KeyCode.W);
        skillDictionary.Add(silverBolts.keyCode, silverBolts);
    }

    // 实现基类中的抽象方法,用于接受访问者对象
    public override void Accept(IVisitor visitor)
    {
        // 调用访问者对象的 Visit 方法,将当前英雄对象传递给访问者
        visitor.Visit(this);
    }
}

// 剑圣英雄类
public class MasterYiHero : Hero
{
    // 在开始时设置英雄名字和技能
    void Start()
    {
        heroName = "无极剑圣易";

        // 创建名为 "alphaStrike" 的技能,并将其添加到技能字典中
        Skill alphaStrike = new AlphaStrike(KeyCode.Q);
        skillDictionary.Add(alphaStrike.keyCode, alphaStrike);

        // 创建名为 "meditate" 的技能,并将其添加到技能字典中
        Skill meditate = new Meditate(KeyCode.W);
        skillDictionary.Add(meditate.keyCode, meditate);
    }

    // 实现基类中的抽象方法,用于接受访问者对象
    public override void Accept(IVisitor visitor)
    {
        // 调用访问者对象的 Visit 方法,将当前英雄对象传递给访问者
        visitor.Visit(this);
    }
}

技能元素类和具体技能类

// 抽象技能基类,所有技能类都继承自此类
public abstract class Skill : IElement
{
    // 技能名称
    public string name;

    // 技能造成的伤害值
    public int damage;

    // 技能恢复生命值
    public int heal;

    // 技能的按键码
    public KeyCode keyCode;

    // 构造函数,初始化技能的按键码
    public Skill(KeyCode keyCode)
    {
        this.keyCode = keyCode;
    }

    // 抽象方法,用于接受访问者对象
    public abstract void Accept(IVisitor visitor);
}

// 闪避突袭技能类
public class Tumble : Skill
{
    public Tumble(KeyCode keyCode) : base(keyCode)
    {
        name = "闪避突袭";
        damage = 50;
        heal = 50;
    }

    public override void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

// 圣银弩箭技能类
public class SilverBolts : Skill
{
    public SilverBolts(KeyCode skillHero) : base(skillHero)
    {
        name = "圣银弩箭";
        damage = 100;
        heal = 0;
    }

    public override void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

// 阿尔法突袭技能类
public class AlphaStrike : Skill
{
    public AlphaStrike(KeyCode skillHero) : base(skillHero)
    {
        name = "阿尔法突袭";
        damage = 100;
        heal = 0;
    }

    public override void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

// 冥想技能类
public class Meditate : Skill
{
    public Meditate(KeyCode skillHero) : base(skillHero)
    {
        name = "冥想";
        damage = 0;
        heal = 100;
    }

    public override void Accept(IVisitor visitor)
    {
        visitor.Visit(this);
    }
}

访问者接口和具体访问者接口

// 访问者接口,定义了访问不同类型对象的方法
public interface IVisitor
{
    // 访问薇恩的方法
    public void Visit(VayneHero vayneHero);

    // 访问剑圣的方法
    public void Visit(MasterYiHero masterYiHero);

    // 访问闪避突袭的方法
    public void Visit(Tumble tumble);

    // 访问圣银箭弩的方法
    public void Visit(SilverBolts silverBolts);

    // 访问阿尔法突袭的方法
    public void Visit(AlphaStrike alphaStrike);

    // 访问冥想的方法
    public void Visit(Meditate meditate);
}

// 伤害访问者类,用于访问不同的英雄和技能对象并输出伤害信息
public class DamageVisitor : IVisitor
{
    // 访问 VayneHero 对象的方法
    public void Visit(VayneHero vayneHero)
    {
        foreach (var skillStructure in vayneHero.skillDictionary)
        {
            // 如果技能造成伤害大于0,则输出相关信息
            if (skillStructure.Value.damage > 0)
            {
                Debug.Log(vayneHero.heroName + "拥有伤害性技能" + skillStructure.Value.name);
            }
        }
    }

    // 访问 MasterYiHero 对象的方法
    public void Visit(MasterYiHero masterYiHero)
    {
        foreach (var skillStructure in masterYiHero.skillDictionary)
        {
            // 如果技能造成伤害大于0,则输出相关信息
            if (skillStructure.Value.damage > 0)
            {
                Debug.Log(masterYiHero.heroName + "拥有伤害性技能" + skillStructure.Value.name);
            }
        }
    }

    // 访问 Tumble 技能对象的方法,输出技能造成的伤害信息
    public void Visit(Tumble tumble)
    {
        Debug.Log(tumble.name + " 对敌人造成 " + tumble.damage + " 点伤害");
    }

    // 访问 SilverBolts 技能对象的方法,输出技能造成的伤害信息
    public void Visit(SilverBolts silverBolts)
    {
        Debug.Log(silverBolts.name + " 对敌人造成 " + silverBolts.damage + " 点伤害");
    }

    // 访问 AlphaStrike 技能对象的方法,输出技能造成的伤害信息
    public void Visit(AlphaStrike alphaStrike)
    {
        Debug.Log(alphaStrike.name + " 对敌人造成 " + alphaStrike.damage + " 点伤害");
    }

    // 访问 Meditate 技能对象的方法,输出技能造成的伤害信息
    public void Visit(Meditate meditate)
    {
        Debug.Log(meditate.name + " 对敌人造成 " + meditate.damage + " 点伤害");
    }
}

// 治疗访问者类,用于访问不同的英雄和技能对象并输出治疗信息
public class HealVisitor : IVisitor
{
    // 访问 VayneHero 对象的方法
    public void Visit(VayneHero vayneHero)
    {
        foreach (var skillStructure in vayneHero.skillDictionary)
        {
            // 如果技能提供治疗效果大于0,则输出相关信息
            if (skillStructure.Value.heal > 0)
            {
                Debug.Log(vayneHero.heroName + "拥有治疗型技能" + skillStructure.Value.name);
            }
        }
    }

    // 访问 MasterYiHero 对象的方法
    public void Visit(MasterYiHero masterYiHero)
    {
        foreach (var skillStructure in masterYiHero.skillDictionary)
        {
            // 如果技能提供治疗效果大于0,则输出相关信息
            if (skillStructure.Value.heal > 0)
            {
                Debug.Log(masterYiHero.heroName + "拥有治疗型技能" + skillStructure.Value.name);
            }
        }
    }

    // 访问 Tumble 技能对象的方法,输出技能对自己的治疗效果信息
    public void Visit(Tumble tumble)
    {
        Debug.Log(tumble.name + " 对自己进行了治疗" + tumble.heal + "血量");
    }

    // 访问 SilverBolts 技能对象的方法,输出技能对队友的治疗效果信息
    public void Visit(SilverBolts silverBolts)
    {
        Debug.Log(silverBolts.name + " 对队友进行了治疗" + silverBolts.heal + "血量");
    }

    // 访问 AlphaStrike 技能对象的方法,输出技能对自己的治疗效果信息
    public void Visit(AlphaStrike alphaStrike)
    {
        Debug.Log(alphaStrike.name + " 对自己进行了治疗" + alphaStrike.heal + "血量");
    }

    // 访问 Meditate 技能对象的方法,输出技能对自己的治疗效果信息
    public void Visit(Meditate meditate)
    {
        Debug.Log(meditate.name + " 对自己进行了治疗" + meditate.heal + "血量");
    }
}

客户端

public class TestVisitorPattern : MonoBehaviour
{
    public VayneHero vayneHero; // Vayne 英雄对象
    public MasterYiHero masterYiHero; // Master Yi 英雄对象

    private Hero currentHero; // 当前英雄对象

    void Start()
    {
        // 在游戏开始时切换到 Vayne 英雄
        SwitchToHero(vayneHero);
    }

    void Update()
    {
        // 检测空格键按下来切换英雄
        if (Input.GetKeyDown(KeyCode.Space))
        {
            if (currentHero == vayneHero)
            {
                SwitchToHero(masterYiHero); // 切换到 Master Yi 英雄
            }
            else
            {
                SwitchToHero(vayneHero); // 切换到 Vayne 英雄
            }
        }

        if (currentHero != null)
        {
            // 检测按键 Q、W 和 E,并执行相应技能或访问者操作
            if (Input.GetKeyDown(KeyCode.Q))
            {
                currentHero.UseSkill(KeyCode.Q); // 执行 Q 技能
            }
            else if (Input.GetKeyDown(KeyCode.W))
            {
                currentHero.UseSkill(KeyCode.W); // 执行 W 技能
            }
            else if (Input.GetKeyDown(KeyCode.E))
            {
                currentHero.Accept(new DamageVisitor()); // 使用伤害访问者
                currentHero.Accept(new HealVisitor()); // 使用治疗访问者
            }
        }
    }

    // 切换到指定英雄的方法
    private void SwitchToHero(Hero hero)
    {
        if (currentHero != null)
        {
            currentHero.enabled = false;
            currentHero.gameObject.SetActive(false);
        }

        currentHero = hero;
        currentHero.enabled = true;
        currentHero.gameObject.SetActive(true);
    }
}

运行结果



转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏