5.模板方法模式

5.行为型模式-模板方法模式


5.1 基础知识

学习难度:2

使用频率:3

总分:7

定义

模板方法模式(Template Method Pattern)定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模版方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

说人话

定义一个骨架,让子类可以重写其中的实现。

结构图

实现步骤

  • 抽象类:定义一个模版方法,定义一些抽象方法和具体方法,模板方法作为骨架按一定顺序调用抽象方法和具体方法。
  • 多个具体类:继承抽象类,重写抽象方法。
  • 客户端:实例化具体类并调用其模板方法。

说明

其实就是面向对象的继承和多态。


5.2 模版代码

抽象类和具体类

// 抽象类定义了算法的骨架
abstract class AbstractClass
{
    // 模板方法定义了算法的结构
    public void TemplateMethod()
    {
        Step1();
        Step2();
        Step3();
    }

    // 这些步骤由子类实现
    protected abstract void Step1();
    protected abstract void Step2();
    protected abstract void Step3();
}

// 具体子类实现抽象类中的步骤
class ConcreteClassA : AbstractClass
{
    protected override void Step1()
    {
        Console.WriteLine("具体类A:步骤1");
    }

    protected override void Step2()
    {
        Console.WriteLine("具体类A:步骤2");
    }

    protected override void Step3()
    {
        Console.WriteLine("具体类A:步骤3");
    }
}

class ConcreteClassB : AbstractClass
{
    protected override void Step1()
    {
        Console.WriteLine("具体类B:步骤1");
    }

    protected override void Step2()
    {
        Console.WriteLine("具体类B:步骤2");
    }

    protected override void Step3()
    {
        Console.WriteLine("具体类B:步骤3");
    }
}

客户端

class Program
{
    static void Main()
    {
        Console.WriteLine("使用具体类A:");
        AbstractClass classA = new ConcreteClassA();
        classA.TemplateMethod();
        // 使用具体类A:
        // 具体类A:步骤1
        // 具体类A:步骤2
        // 具体类A:步骤3

        Console.WriteLine("使用具体类B:");
        AbstractClass classB = new ConcreteClassB();
        classB.TemplateMethod();
        // 使用具体类B:
        // 具体类B:步骤1
        // 具体类B:步骤2
        // 具体类B:步骤3
    }
}

5.3 CSharp实践

实践需求

使用模版方法模式,模拟制作咖啡和茶。

饮料类、咖啡和茶

// 饮料制作是抽象类,它定义了通用的制作过程
abstract class Beverage
{
    public void PrepareBeverage()
    {
        BoilWater(); // 煮沸水
        Brew(); // 冲泡
        PourInCup(); // 倒入杯中
        AddCondiments(); // 加入调料
    }

    public void BoilWater()
    {
        Console.WriteLine("将水煮沸");
    }

    // 子类需要实现的步骤
    protected abstract void Brew(); // 冲泡

    public void PourInCup()
    {
        Console.WriteLine("倒入杯中");
    }

    // 子类需要实现的步骤
    protected abstract void AddCondiments(); // 加入调料
}

// 具体子类咖啡
class Coffee : Beverage
{
    protected override void Brew()
    {
        Console.WriteLine("冲泡咖啡");
    }

    protected override void AddCondiments()
    {
        Console.WriteLine("加入糖和牛奶");
    }
}

// 具体子类茶
class Tea : Beverage
{
    protected override void Brew()
    {
        Console.WriteLine("泡制茶叶");
    }

    protected override void AddCondiments()
    {
        Console.WriteLine("加入柠檬");
    }
}

客户端

class Program
{
    static void Main()
    {
        Console.WriteLine("制作咖啡:");
        Beverage coffee = new Coffee();
        coffee.PrepareBeverage();
        // 制作咖啡:
        // 将水煮沸
        // 冲泡咖啡
        // 倒入杯中
        // 加入糖和牛奶
        
        Console.WriteLine("制作茶:");
        Beverage tea = new Tea();
        tea.PrepareBeverage();
        // 制作茶:
        // 将水煮沸
        // 泡制茶叶
        // 倒入杯中 
        // 加入柠檬
    }
}

5.4 Unity实践

实践需求

使用模版方法模式,实现狗头和剑圣可以随机传送位置和回城,回城成功后播放跳舞动画。

自定义计时器类

// 自定义计时器
public class MyTimer : MonoBehaviorSingleton<MyTimer>
{
    // 延时执行函数
    public void DelayedFunc(int delayInMillis, Action delayedFunction)
    {
        Timer timer = null;

        // 创建一个定时器,延时执行指定的 delayedFunction
        timer = new Timer(_ =>
        {
            delayedFunction.Invoke(); // 执行延时函数
            timer.Dispose(); // 释放定时器资源
        }, null, delayInMillis, Timeout.Infinite);
    }

    // 使用协程来执行延时函数
    public void StartDelayedFuncCoroutine(float delayInSeconds, Action delayedFunction)
    {
        // 调用协程函数
        StartCoroutine(DelayedFuncCoroutine(delayInSeconds, delayedFunction));
    }

    // 基于协程的延时函数实现
    private IEnumerator DelayedFuncCoroutine(float delayInSeconds, Action delayedFunction)
    {
        yield return new WaitForSeconds(delayInSeconds); // 等待一段时间

        delayedFunction?.Invoke(); // 执行延时函数(如果不为null)
    }

    // 使用协程来执行一定帧数后的函数
    public void StartDelayedFrameFuncCoroutine(int delayFrame, Action delayedFunction)
    {
        if (delayedFunction == null)
        {
            Debug.LogWarning("Delayed function is null."); // 如果延时函数为null,输出警告
            return;
        }

        // 调用协程函数
        StartCoroutine(DelayedFrameFuncCoroutine(delayFrame, delayedFunction));
    }

    // 基于协程的帧数延时函数实现
    private IEnumerator DelayedFrameFuncCoroutine(int delayFrame, Action delayedFunction)
    {
        int d = delayFrame;
        while (d > 0)
        {
            yield return null; // 等待一帧
            d--;
        }

        delayedFunction.Invoke(); // 执行延时函数
    }
}

抽象英雄类和具体英雄类

// 抽象英雄类,定义了一些英雄行为的框架
public abstract class AbstractHeroClass
{
    protected GameObject heroGameObject; // 保存英雄的游戏对象
    protected Animator heroAnimator;    // 保存英雄的动画控制器

    protected delegate void CallBack(); // 用于定义回调函数的委托类型

    protected AbstractHeroClass(GameObject heroGameObject)
    {
        this.heroGameObject = heroGameObject; // 构造函数,初始化英雄游戏对象
        heroAnimator = heroGameObject.GetComponent<Animator>(); // 获取英雄的动画控制器
    }

    // 英雄回城的方法
    public void BackHome()
    {
        BackHomeAnim(() =>
        {
            Debug.Log("在 BackHomeAnim 的回调函数中执行一些操作");
            BackHomePos(() =>
            {
                Debug.Log("在 BackHomePos 的回调函数中执行一些操作");
                IdleAnim(null);
            });
        });
    }

    // 随机移动到一个位置
    public void GoToRandomPos()
    {
        heroGameObject.transform.position = new Vector3(Random.Range(-4f, 4f), 0, 0);
        Debug.Log($"英雄随机位置设置为{heroGameObject.transform.position}");
    }

    // 移动到家的位置
    public void GoToHomePos()
    {
        heroGameObject.transform.position = new Vector3(0, 0, 0);
        Debug.Log($"英雄回城位置设置为{heroGameObject.transform.position}");
    }

    // 抽象方法,用于在具体英雄子类中实现不同的回城动画
    protected abstract void BackHomeAnim(CallBack callBack);

    // 抽象方法,用于在具体英雄子类中实现回城后的位置变化
    protected abstract void BackHomePos(CallBack callBack);

    // 抽象方法,用于在具体英雄子类中实现待机动画
    protected abstract void IdleAnim(CallBack callBack);
}

// 狗头具体英雄类
public class ConcreteDogHeroClass : AbstractHeroClass
{
    public ConcreteDogHeroClass(GameObject heroGameObject) : base(heroGameObject)
    {
    }

    // 实现抽象方法 BackHomeAnim,用于狗头回城的动画
    protected override void BackHomeAnim(CallBack callBack)
    {
        heroAnimator.SetBool("backhome", true); // 设置回城动画为true
        heroAnimator.SetBool("idle", false);     // 设置默认动画为false

        // 使用 MyTimer 实例执行延时函数,模拟回城动画的持续时间为3秒
        MyTimer.GetInstance().StartDelayedFuncCoroutine(3, (() =>
        {
            Debug.Log("狗头回城动画完成");
            callBack?.Invoke(); // 执行回调函数(如果不为null)
        }));
    }

    // 实现抽象方法 BackHomePos,用于设置狗头回城后的位置
    protected override void BackHomePos(CallBack callBack)
    {
        GoToHomePos(); // 调用父类方法,将狗头移动到家的位置
        Debug.Log($"狗头回城位置设置为{heroGameObject.transform.position}");
        callBack?.Invoke(); // 执行回调函数(如果不为null)
    }

    // 实现抽象方法 IdleAnim,用于播放狗头的默认动画
    protected override void IdleAnim(CallBack callBack)
    {
        heroAnimator.SetBool("backhome", false); // 设置回城动画为false
        heroAnimator.SetBool("idle", true);      // 设置默认动画为true
        Debug.Log("狗头播放默认动画");
    }
}

// 剑圣具体英雄类
public class ConcreteYiHeroClass : AbstractHeroClass
{
    public ConcreteYiHeroClass(GameObject heroGameObject) : base(heroGameObject)
    {
    }

    // 实现抽象方法 BackHomeAnim,用于剑圣回城的动画
    protected override void BackHomeAnim(CallBack callBack)
    {
        heroAnimator.SetBool("backhome", true); // 设置回城动画为 true
        heroAnimator.SetBool("idle", false);     // 设置默认动画为 false

        // 使用 MyTimer 实例执行延时函数,模拟回城动画的持续时间为 3 秒
        MyTimer.GetInstance().StartDelayedFuncCoroutine(3, (() =>
        {
            Debug.Log("剑圣回城动画完成");
            callBack?.Invoke(); // 执行回调函数(如果不为 null)
        }));
    }

    // 实现抽象方法 BackHomePos,用于设置剑圣回城后的位置
    protected override void BackHomePos(CallBack callBack)
    {
        GoToHomePos(); // 调用父类方法,将剑圣移动到家的位置
        Debug.Log($"剑圣回城位置设置为{heroGameObject.transform.position}");
        callBack?.Invoke(); // 执行回调函数(如果不为 null)
    }

    // 实现抽象方法 IdleAnim,用于播放剑圣的默认动画
    protected override void IdleAnim(CallBack callBack)
    {
        heroAnimator.SetBool("backhome", false); // 设置回城动画为 false
        heroAnimator.SetBool("idle", true);      // 设置默认动画为 true
        Debug.Log("剑圣播放默认动画");
    }
}

客户端

public class TestTemplateMethodPattern : MonoBehaviour
{
    public GameObject dogPrefab; // 狗头英雄的预制体
    public GameObject yiPrefab; // 剑圣英雄的预制体

    private GameObject dogGameObject; // 狗头英雄的游戏对象
    private GameObject yiGameObject; // 剑圣英雄的游戏对象

    public AbstractHeroClass nowHero; // 当前选择的英雄
    public ConcreteDogHeroClass dogHero; // 具体的狗头英雄
    public ConcreteYiHeroClass yiHero; // 具体的剑圣英雄

    void Start()
    {
        dogGameObject = Instantiate(dogPrefab); // 创建狗头英雄的游戏对象
        dogHero = new ConcreteDogHeroClass(dogGameObject); // 创建狗头英雄实例

        yiGameObject = Instantiate(yiPrefab); // 创建剑圣英雄的游戏对象
        yiHero = new ConcreteYiHeroClass(yiGameObject); // 创建剑圣英雄实例

        dogGameObject.SetActive(true); // 激活狗头英雄游戏对象
        yiGameObject.SetActive(false); // 禁用剑圣英雄游戏对象
        SetNowHero(); // 设置当前英雄
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.LeftShift) || Input.GetKeyDown(KeyCode.RightShift))
        {
            ChangeHero(); // 切换当前英雄
        }

        if (Input.GetKeyDown(KeyCode.Space))
        {
            nowHero.GoToRandomPos(); // 当前英雄移动到随机位置
        }

        if (Input.GetKeyDown(KeyCode.B))
        {
            nowHero.BackHome(); // 当前英雄回城
        }
    }

    // 切换当前英雄(激活/禁用不同英雄的游戏对象)
    void ChangeHero()
    {
        dogGameObject.SetActive(!dogGameObject.activeSelf);
        yiGameObject.SetActive(!yiGameObject.activeSelf);
        SetNowHero(); // 设置当前英雄
    }

    // 设置当前英雄为激活的英雄
    void SetNowHero()
    {
        if (dogGameObject.activeSelf)
        {
            nowHero = dogHero;
        }
        else if (yiGameObject.activeSelf)
        {
            nowHero = yiHero;
        }
        else
        {
            nowHero = null;
        }
    }
}

其他准备工作

准备好狗头和剑圣的动画状态机和预制体

运行结果







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

×

喜欢就点赞,疼爱就打赏