Unity 设计模式 之 行为型模式 -【访问者模式】【模板模式】【策略模式】

目录

Unity 设计模式 之 行为型模式 -【访问者模式】【模板模式】【策略模式】

一、简单介绍

二、访问者模式(Visitor Pattern)

1、什么时候使用访问者模式

2、使用访问者模式的好处

3、使用访问者模式时的注意事项

三、在 Unity 中使用 访问者模式

1、定义元素接口(IShapeElement)

2、定义具体的 3D 对象类(Cube 和 Sphere)

3、定义访问者接口(IShapeVisitor)

4、实现具体访问者类(ShapeRenderer 和 ShapeScaler)

5、客户端代码(VisitorPatternExample)

6、运行示例

四、模板方法模式(Template Method Pattern)

1、什么时候使用模板方法模式

2、使用模板模式的好处

3、使用模板方法模式时的注意事项

五、在 Unity 中使用 模板模式

1、设计角色初始化的模板方法模式

2、 客户端代码

3、运行示例

六、策略模式(Strategy Pattern)

1、什么时候使用策略模式

2、使用策略模式的好处

3、使用策略模式时的注意事项

七、在 Unity 中使用 策略模式

1、 策略模式的实现步骤

(1) 定义策略接口 IMoveStrategy

(2) 定义具体策略类 WalkStrategy、RunStrategy 和 JumpStrategy

(3) 定义上下文类 Character

2、 客户端代码

3、示例解释

4、运行示例


一、简单介绍

设计模式 是指在软件开发中为解决常见问题而总结出的一套 可复用的解决方案。这些模式是经过长期实践证明有效的 编程经验总结,并可以在不同的项目中复用。设计模式并不是代码片段,而是对常见问题的 抽象解决方案,它提供了代码结构和模块间交互的一种设计思路,帮助开发者解决特定的设计问题。

二、访问者模式(Visitor Pattern)

访问者模式(Visitor Pattern) 是一种行为型设计模式,它允许你在不修改已有类的前提下,向这些类添加新的功能。该模式通过将操作逻辑与对象结构分离,使得你可以在不改变类结构的情况下为对象添加新功能。

访问者模式的关键是定义一个访问者接口,包含一组针对不同类的访问操作。然后,将访问者传递给对象,让对象接受访问者并调用相应的操作。

1、什么时候使用访问者模式

  1. 对象结构相对稳定,操作行为频繁变化:如果你的对象结构是稳定的,但需要频繁添加新功能,那么访问者模式非常适合。它可以在不修改原始类的前提下添加新的操作。

  2. 需要对不同类型的对象执行不同操作:当你有一组类型各异的对象,并且希望对这些对象执行多种不同的操作时,访问者模式可以使操作代码与对象结构分离,避免在对象中添加大量的 if-elseswitch-case 语句。

  3. 复杂对象结构:在复杂的对象层次结构中,访问者模式通过集中管理操作,使得你可以避免修改对象层次结构中的每个类。

2、使用访问者模式的好处

  1. 增加新功能而不修改原有代码:访问者模式使得在不修改已有类的情况下,轻松扩展新的操作或行为,符合开闭原则

  2. 分离行为与数据结构:它将操作逻辑与数据结构分离,维护起来更加灵活。在复杂系统中,尤其是涉及多个对象类型时,这种模式非常有用。

  3. 清晰的责任划分:可以将不同的操作封装到不同的访问者类中,使得每个访问者负责一类操作,职责划分更加明确。

  4. 支持不同的操作顺序:通过不同的访问者,可以灵活控制操作顺序,适用于需要多次遍历不同类对象并进行操作的情况。

3、使用访问者模式时的注意事项

  1. 违反依赖倒置原则:访问者模式将具体访问者的操作逻辑硬编码在对象类中,可能会导致对象类对访问者的强依赖。

  2. 增加复杂性:访问者模式虽然带来扩展性,但它也增加了系统的复杂性,尤其是对象结构较为复杂时,每个元素类都需要提供 Accept 方法。

  3. 访问者与对象耦合:当对象结构频繁变化时,访问者模式并不合适。因为每次对象结构变化时,所有访问者都需要做出相应的调整,违背了扩展性原则。

  4. 类型安全性问题:在访问者模式中,访问者接口方法通常针对不同的对象类型,因此需要考虑类型安全性,避免类型转换错误。

三、在 Unity 中使用 访问者模式

在 Unity 中,访问者模式可以用于在不同类型的 3D 对象(例如立方体、球体等)上执行特定操作,而无需修改这些对象的代码。以下是一个使用访问者模式的示例,展示如何在 Unity 中渲染不同的 3D 对象,并通过访问者模式实现不同操作,比如渲染、缩放等。

参考类图如下:

Unity 设计模式 之 行为型模式 -【访问者模式】【模板模式】【策略模式】-LMLPHP

1、定义元素接口(IShapeElement)

首先,我们定义一个元素接口 IShapeElement,3D 对象(立方体、球体等)将实现该接口,并接受访问者的操作。

public interface IShapeElement
{
    void Accept(IShapeVisitor visitor);
}
2、定义具体的 3D 对象类(Cube 和 Sphere)

我们定义立方体和球体类,这些类实现了 IShapeElement 接口,并提供 Accept 方法来接受访问者。

using UnityEngine;

public class Cube : IShapeElement
{
    private GameObject cubeObject;

    public Cube()
    {
        cubeObject = GameObject.CreatePrimitive(PrimitiveType.Cube);
        cubeObject.name = "Cube";
        cubeObject.transform.position = new Vector3(-2, 0, 0); // 设置初始位置
    }

    public void Accept(IShapeVisitor visitor)
    {
        visitor.VisitCube(this); // 立方体接受访问者
    }

    public void SetScale(Vector3 scale)
    {
        cubeObject.transform.localScale = scale; // 设置立方体缩放
    }

    public void SetColor(Color color)
    {
        cubeObject.GetComponent<Renderer>().material.color = color; // 设置立方体颜色
    }
}

public class Sphere : IShapeElement
{
    private GameObject sphereObject;

    public Sphere()
    {
        sphereObject = GameObject.CreatePrimitive(PrimitiveType.Sphere);
        sphereObject.name = "Sphere";
        sphereObject.transform.position = new Vector3(2, 0, 0); // 设置初始位置
    }

    public void Accept(IShapeVisitor visitor)
    {
        visitor.VisitSphere(this); // 球体接受访问者
    }

    public void SetScale(Vector3 scale)
    {
        sphereObject.transform.localScale = scale; // 设置球体缩放
    }

    public void SetColor(Color color)
    {
        sphereObject.GetComponent<Renderer>().material.color = color; // 设置球体颜色
    }
}
3、定义访问者接口(IShapeVisitor)

我们定义访问者接口 IShapeVisitor,其中包含针对不同 3D 对象的访问操作。

public interface IShapeVisitor
{
    void VisitCube(Cube cube);
    void VisitSphere(Sphere sphere);
}
4、实现具体访问者类(ShapeRenderer 和 ShapeScaler)

接下来,我们创建两个具体的访问者类,分别用于渲染和缩放 3D 对象。

4.1 渲染访问者

using UnityEngine;

public class ShapeRenderer : IShapeVisitor
{
    public void VisitCube(Cube cube)
    {
        Debug.Log("Rendering Cube");
        cube.SetColor(Color.red); // 渲染时将立方体设为红色
    }

    public void VisitSphere(Sphere sphere)
    {
        Debug.Log("Rendering Sphere");
        sphere.SetColor(Color.blue); // 渲染时将球体设为蓝色
    }
}

4.2 缩放访问者

using UnityEngine;

public class ShapeScaler : IShapeVisitor
{
    public void VisitCube(Cube cube)
    {
        Debug.Log("Scaling Cube");
        cube.SetScale(new Vector3(2, 2, 2)); // 缩放立方体
    }

    public void VisitSphere(Sphere sphere)
    {
        Debug.Log("Scaling Sphere");
        sphere.SetScale(new Vector3(1.5f, 1.5f, 1.5f)); // 缩放球体
    }
}
5、客户端代码(VisitorPatternExample)

最后,我们在客户端代码中创建 3D 对象,并使用访问者来执行渲染和缩放操作。

using UnityEngine;

public class VisitorPatternExample : MonoBehaviour
{
    void Start()
    {
        // 创建立方体和球体
        IShapeElement cube = new Cube();
        IShapeElement sphere = new Sphere();

        // 创建访问者
        IShapeVisitor renderer = new ShapeRenderer();
        IShapeVisitor scaler = new ShapeScaler();

        // 渲染并缩放
        cube.Accept(renderer); // 立方体接受渲染
        cube.Accept(scaler);   // 立方体接受缩放

        sphere.Accept(renderer); // 球体接受渲染
        sphere.Accept(scaler);   // 球体接受缩放
    }
}

6、运行示例

  1. 在 Unity 中,创建一个空的 GameObject,并将 VisitorPatternExample 脚本附加到该对象上。
  2. 运行场景,你将看到:
    • 一个红色立方体和一个蓝色球体。
    • 立方体被缩放到 2 倍大小,球体被缩放到 1.5 倍大小。

通过这个示例,展示了如何在 Unity 中利用访问者模式对 3D 对象执行不同的操作,并保持系统的可扩展性。访问者模式,将操作分离到了不同的访问者类中,而不需要修改具体的 3D 对象类(如 CubeSphere)。这使得我们可以方便地添加新的操作,比如其他渲染效果或物理操作。访问者模式允许在不修改现有类的情况下添加新功能,特别适合对象结构稳定、操作不断变化的场景。在 Unity 中,访问者模式可以用来处理不同的 3D 对象,并在这些对象上执行多种操作,例如渲染、缩放等。

四、模板方法模式(Template Method Pattern)

模板方法模式(Template Method Pattern)是一种行为型设计模式,它定义了一个算法的骨架(步骤顺序),并允许子类在不改变算法结构的情况下重定义算法的某些步骤。换句话说,它将算法的通用部分放在基类中,而具体的实现细节则由子类去完成。

在模板方法模式中,父类提供了一个模板方法,定义了一系列操作的顺序,而子类可以根据需要重写这些操作以实现不同的行为。

模板方法模式的结构

1、什么时候使用模板方法模式

  1. 多个类有相同的算法结构,但是算法的具体步骤在不同类中有所不同时,可以使用模板方法模式将算法的共同结构提取到基类中。

  2. 子类中的方法实现有固定的顺序,即在不同子类中,操作的执行顺序是相同的,但是具体实现有所不同。

  3. 需要复用算法中的通用逻辑,但允许部分操作由子类定制时,使用模板方法模式可以达到代码复用和灵活性的平衡。

  4. 算法具有固定的流程,但其某些步骤需要根据不同情况定制。例如游戏开发中的某些 AI 行为、UI 组件的初始化顺序等。

2、使用模板模式的好处

  1. 代码复用:模板方法模式将算法的通用部分提取到基类中,避免子类重复实现这些通用逻辑。子类只需实现差异化的部分,从而减少了代码重复,提高了代码的复用性。

  2. 控制反转:父类控制算法的整体流程,子类只需关注具体步骤的实现。这样子类的灵活性得到保障,同时父类可以保证流程的稳定性。

  3. 易于扩展:通过继承基类并实现不同的步骤,模板方法模式提供了一种灵活的扩展机制,符合开闭原则(对扩展开放,对修改关闭)。

  4. 分离不变与可变的部分:将算法中不变的部分放入父类,变化的部分留给子类实现,清晰地分离了两者的职责。

3、使用模板方法模式时的注意事项

  1. 增加类的复杂性:模板方法模式可能会增加类的数量和复杂性,尤其是在层次结构比较深的时候,导致可读性下降。

  2. 强依赖继承:模板方法模式依赖于继承,如果系统中广泛使用继承,会导致子类与父类之间的紧密耦合。如果继承层次过深,维护起来可能会变得困难。

  3. 方法实现的约束性:模板方法强制执行某些步骤的顺序,可能会限制子类的灵活性,尤其是当子类的需求不同于模板流程时,修改模板方法会比较困难。

  4. 保护方法的可见性:为了防止子类随意修改模板中的关键步骤,一些步骤通常定义为 protected,这样子类可以重写它们但不能直接调用。

五、在 Unity 中使用 模板模式

在 Unity 中,我们可以使用模板方法模式来设计一个游戏角色的初始化流程,允许不同的角色类型(如战士、法师、弓箭手等)有不同的外观、行为和初始化步骤。通过模板方法模式,保持角色初始化流程的一致性,同时允许不同的角色有其独特的行为。

参考类图如下:

Unity 设计模式 之 行为型模式 -【访问者模式】【模板模式】【策略模式】-LMLPHP

1、设计角色初始化的模板方法模式

(1) 定义抽象类 Character

首先定义一个抽象类 Character,它提供一个模板方法 InitializeCharacter 来定义角色初始化的流程。具体的子类可以通过重写其中的一些方法,定制角色的外观、出生动画以及其他行为。

using UnityEngine;

public abstract class Character : MonoBehaviour
{
    // 模板方法,定义角色初始化流程
    public void InitializeCharacter()
    {
        LoadModel();           // 加载角色模型
        SetPosition();         // 设置初始位置
        PlaySpawnAnimation();  // 播放出生动画
        EquipWeapon();         // 装备武器
        CustomBehavior();      // 子类自定义行为
    }

    // 具体方法,所有角色都共享
    private void LoadModel()
    {
        Debug.Log("Loading character model...");
    }

    // 具体方法,所有角色都共享
    private void SetPosition()
    {
        transform.position = Vector3.zero;
        Debug.Log("Setting character position to zero.");
    }

    // 虚方法,子类可以重写
    protected virtual void PlaySpawnAnimation()
    {
        Debug.Log("Playing default spawn animation.");
    }

    // 抽象方法,必须由子类实现
    protected abstract void EquipWeapon();

    // 抽象方法,必须由子类实现
    protected abstract void CustomBehavior();
}

(2) 定义具体角色类 WarriorMage

创建两个具体的角色类 WarriorMage,它们继承自 Character 类,并提供自己独特的实现。例如,战士将使用剑和盾牌,法师则使用法杖和魔法。

public class Warrior : Character
{
    // 重写出生动画
    protected override void PlaySpawnAnimation()
    {
        Debug.Log("Playing warrior spawn animation.");
    }

    // 实现装备武器的逻辑
    protected override void EquipWeapon()
    {
        Debug.Log("Warrior equipped with sword and shield.");
    }

    // 实现自定义行为
    protected override void CustomBehavior()
    {
        Debug.Log("Warrior is ready to fight.");
    }
}

public class Mage : Character
{
    // 重写出生动画
    protected override void PlaySpawnAnimation()
    {
        Debug.Log("Playing mage spawn animation.");
    }

    // 实现装备武器的逻辑
    protected override void EquipWeapon()
    {
        Debug.Log("Mage equipped with staff and spellbook.");
    }

    // 实现自定义行为
    protected override void CustomBehavior()
    {
        Debug.Log("Mage is casting a spell.");
    }
}

2、 客户端代码

GameInitializer 中初始化战士和法师角色,调用模板方法 InitializeCharacter 来执行角色初始化的完整流程。

using UnityEngine;

public class GameInitializer : MonoBehaviour
{
    void Start()
    {
        // 创建并初始化战士角色
        Character warrior = gameObject.AddComponent<Warrior>();
        warrior.InitializeCharacter();

        // 创建并初始化法师角色
        Character mage = gameObject.AddComponent<Mage>();
        mage.InitializeCharacter();
    }
}

3、运行示例

当你运行此代码时,Unity 控制台将显示战士和法师的初始化流程,具体输出如下:

Loading character model...
Setting character position to zero.
Playing warrior spawn animation.
Warrior equipped with sword and shield.
Warrior is ready to fight.

Loading character model...
Setting character position to zero.
Playing mage spawn animation.
Mage equipped with staff and spellbook.
Mage is casting a spell.

每个角色都按照相同的初始化顺序进行,首先加载模型并设置位置,接着播放各自独特的出生动画、装备武器,并执行自定义行为。

模板方法模式 在 Unity 中应用时,可以用于将游戏角色的初始化、行为逻辑抽象出来,确保不同角色的初始化流程一致,但允许每个角色有其独特的表现。

六、策略模式(Strategy Pattern)

策略模式(Strategy Pattern)是一种行为型设计模式,定义了一系列算法,并将每种算法封装到独立的类中,使得它们可以互相替换。策略模式让算法可以在不影响客户端的情况下独立变化,客户端通过与这些策略对象进行交互来执行不同的行为。

在策略模式中,核心思想是将算法的定义和使用分离,使得不同的策略(算法)可以灵活地切换,符合面向对象设计中的开闭原则,即对扩展开放、对修改关闭。

策略模式的结构

1、什么时候使用策略模式

  1. 需要多个算法或行为可以相互替换时,策略模式非常适合。例如在游戏中,角色的攻击方式、移动方式等可以作为不同的策略。

  2. 算法的实现会频繁变化时,策略模式可以有效应对需求变更,允许在不影响上下文类的情况下独立修改或增加算法。

  3. 需要避免多重条件语句时,使用策略模式可以消除复杂的条件判断逻辑。

  4. 不同的策略可以在运行时灵活切换时,比如支付方式的选择(如信用卡、PayPal、银行转账等),适合使用策略模式来实现。

2、使用策略模式的好处

  1. 算法的灵活切换:策略模式使得在运行时可以根据需要动态地切换不同的算法,不需要修改上下文类的代码,从而增强了系统的灵活性。

  2. 符合开闭原则:策略模式通过将算法的实现抽象化,避免了在修改或添加新策略时修改已有代码,系统更容易扩展。

  3. 避免条件语句:策略模式消除了在代码中频繁使用的条件判断语句(如 if-elseswitch 语句),将算法的不同实现封装在不同的策略类中,降低了复杂性。

  4. 提高代码可维护性:策略模式通过分离算法,使每种算法独立封装,不同的策略彼此独立,便于测试和维护。

3、使用策略模式时的注意事项

  1. 增加对象数量:策略模式会引入多个策略类,这在某些情况下会导致类的数量增加,增加了代码的复杂性。

  2. 客户端必须了解不同的策略:客户端必须清楚地知道有哪些可用的策略,并且需要选择合适的策略。这可能会增加客户端的复杂度。

  3. 策略的独立性:虽然策略模式提供了灵活性,但策略类之间应该保持独立。如果策略类之间存在依赖关系,可能会违背策略模式的初衷。

七、在 Unity 中使用 策略模式

在 Unity 中使用策略模式的一个典型应用场景是为游戏角色定义不同的移动方式,例如:走路、跑步、跳跃等。在游戏开发中,不同的角色可能有不同的移动方式。通过策略模式,我们可以将这些不同的移动逻辑封装到独立的策略类中,并动态切换角色的移动方式。

参考类图如下:

Unity 设计模式 之 行为型模式 -【访问者模式】【模板模式】【策略模式】-LMLPHP

1、 策略模式的实现步骤

(1) 定义策略接口 IMoveStrategy

首先定义一个策略接口 IMoveStrategy,它规定所有移动策略都必须实现一个 Move() 方法。

public interface IMoveStrategy
{
    void Move(Transform characterTransform);
}
(2) 定义具体策略类 WalkStrategy、RunStrategy 和 JumpStrategy

创建几个具体的移动策略类 WalkStrategyRunStrategyJumpStrategy,分别实现不同的移动行为。

using UnityEngine;

// 走路策略
public class WalkStrategy : IMoveStrategy
{
    public void Move(Transform characterTransform)
    {
        characterTransform.Translate(Vector3.forward * 2f * Time.deltaTime);
        Debug.Log("Character is walking.");
    }
}

// 跑步策略
public class RunStrategy : IMoveStrategy
{
    public void Move(Transform characterTransform)
    {
        characterTransform.Translate(Vector3.forward * 5f * Time.deltaTime);
        Debug.Log("Character is running.");
    }
}

// 跳跃策略
public class JumpStrategy : IMoveStrategy
{
    public void Move(Transform characterTransform)
    {
        characterTransform.Translate(Vector3.up * 5f * Time.deltaTime);
        Debug.Log("Character is jumping.");
    }
}
(3) 定义上下文类 Character

Character 类作为上下文类,负责维护当前的移动策略,并提供方法来设置不同的移动策略和执行移动操作。

using UnityEngine;

public class Character : MonoBehaviour
{
    private IMoveStrategy moveStrategy;

    // 设置移动策略
    public void SetMoveStrategy(IMoveStrategy strategy)
    {
        moveStrategy = strategy;
    }

    // 执行移动
    public void PerformMove()
    {
        if (moveStrategy != null)
        {
            moveStrategy.Move(transform);
        }
        else
        {
            Debug.LogWarning("Move strategy not set!");
        }
    }
}

2、 客户端代码

GameController 中,我们实例化角色,并动态设置和切换移动策略。

using UnityEngine;

public class GameController : MonoBehaviour
{
    private Character character;

    void Start()
    {
        // 创建角色
        character = gameObject.AddComponent<Character>();

        // 初始化为走路策略
        character.SetMoveStrategy(new WalkStrategy());
    }

    void Update()
    {
        // 执行移动
        character.PerformMove();

        // 根据输入切换策略
        if (Input.GetKeyDown(KeyCode.W))
        {
            character.SetMoveStrategy(new WalkStrategy());
        }
        else if (Input.GetKeyDown(KeyCode.R))
        {
            character.SetMoveStrategy(new RunStrategy());
        }
        else if (Input.GetKeyDown(KeyCode.Space))
        {
            character.SetMoveStrategy(new JumpStrategy());
        }
    }
}

3、示例解释

  • WalkStrategy:角色以较慢的速度向前移动,模拟走路的行为。
  • RunStrategy:角色以较快的速度向前移动,模拟跑步的行为。
  • JumpStrategy:角色向上跳跃,模拟跳跃的行为。

GameController 中,使用键盘输入(W 键、R 键和空格键)来动态切换角色的移动策略。按下相应的键后,角色将切换到走路、跑步或跳跃模式。

4、运行示例

  • 当按下 W 键时,角色开始以走路的方式移动,速度较慢。
  • 当按下 R 键时,角色开始以跑步的方式移动,速度加快。
  • 当按下 Space 键时,角色执行跳跃动作。

通过策略模式,角色的移动方式可以灵活切换,而不需要修改任何核心代码。

策略模式 在 Unity 中的优势是可以动态切换角色行为,如移动、攻击等。通过将不同的移动方式封装到独立的类中,我们可以更方便地扩展系统,并根据游戏需求随时切换策略。

09-24 10:54