装饰器模式实践:告别臃肿的继承链,优雅解耦初始化状态管理

一、从一个需求开始

1.1 需求

让一个类有一个Init方法,并且类的其他方法中,都要确定类被初始化后才走逻辑,否则直接返回。

1.2 常规实现

角色上有一个顶层控制器CharacterController类。

角色上还有一堆功能,比如WeaponController、MoveController等等。

每个Controller都需要一个初始化方法,通常大家会在Start、OnEnable或Awake中进行初始化。

但是有些时候我们希望将初始化交给别的类进行如Controller.Init(XXX param)的带参数的初始化。

那么代码可能就变成了这样:

public class CharacterInitParam
{
     public string characterName;

}

public class Character : Monobehaviour
{
        private string _name;
	// 这个类是否被初始化了
	private bool isInited  =false;

    public void Init<T>(T param)
    {

        if(param is CharacterInitParam)
        {
                   var param = param as CharacterInitParam;
                   _name = param.characterName;
        }
		isInited = true;

    }

	public void DebugCharacter()
	{
		if(!isInited)
		{
			Debug.LogError("Character没有被初始化");
			return;
		}

		// Dosomthing...
	}
}

重点是,让类中的很多方法都要保证在类被初始化完成后才能被进行调用。

1.3  问题

1. 每个类都要写自己的Init方法,我们应该用接口进行行为约束,保证每个初始化方法都叫Init,代表对类的初始化。

2. 每个类都要维护一个private bool isInited 状态。

二、先利用接口、继承解决

2.1 声明初始化接口

    // 初始化接口
    public interface IInitializable<T> where T : class
    {
        void Init(T initData);
    }

2.2 一个Base类

public abstract class BaseInit<T> : MonoBehaviour, IInitializable<T> where T : class
{
    protected bool _isInitialized;
    protected T _initData;

    public void Init(T initData)
    {
        _initData = initData;
        _isInitialized = true;
    }
}

2.3 Character实现和调用例子

public class CharacterInitParam
    {
        public string name;
    }

    public class Character : BaseInit<CharacterInitParam>
    {
        public void DebugCharacter()
        {
            if (_isInitialized)
            {
                Debug.Log($"Character name:{name}");
            }
        }
    }

    public class ASystem
    {
        public Character character;

        public void Test()
        {
            if (character != null)
            {
                // 因为没有初始化所以这里不会打印。
                character.DebugCharacter();
            }
        }
    }

2.4 问题

因为c#对class不能多继承,所以一个BaseInit满足不了现实中的需求,我们的父类可能存在更多的功能,所以整个继承链会很长,随着需求的增多,继承链和继承类的内容要被频繁重新规划。

三、利用装饰器模式对初始化功能进行解耦,不搞继承链

3.1 新建初始化装饰器

// 初始化状态检查装饰器
    public class InitializationCheckingDecorator<T> : IInitializable<T> where T : class
    {
        private readonly IInitializable<T> _wrappedComponent;
        public bool IsInited { get; private set; } = false;

        public InitializationCheckingDecorator(IInitializable<T> component)
        {
            _wrappedComponent = component;
        }

        public void Init(T initData)
        {
            if (IsInited)
            {
                throw new InvalidOperationException($"[InitCheck] {_wrappedComponent.GetType().Name} has already been initialized.");
            }
            _wrappedComponent.Init(initData);
            IsInited = true;
        }

        // 提供一个方法来执行需要初始化状态检查的操作
        public void ExecuteIfInitialized(Action<IInitializable<T>> action)
        {
            if (!IsInited)
            {
                throw new InvalidOperationException($"[InitCheck] {_wrappedComponent.GetType().Name} must be initialized before this operation.");
            }
            action(_wrappedComponent);
        }
    }

3.2 使用

private CharacterController _Player;
private InitializationCheckingDecorator<CharacterControllerInitData> _playerDecorator;

private void SomewhereInitCharacter()
{
    _Player = character.GetComponent<CharacterController>();
    if (_Player == null)
    {
        throw new Exception("[MenuPreviewState] Can't find character controller:" + defaultCharacter.PrefabPath);
    }
    // Init装饰器
    _playerDecorator = new InitializationCheckingDecorator<CharacterControllerInitData>(_Player);
    _playerDecorator.Init(new CharacterControllerInitData() { LubanCharacterConfig = defaultCharacter });
    _playerDecorator.ExecuteIfInitialized(player =>
    {
          // 设置idle模式
          _Player.SetMode(EnumCharacterControlMode.MenuAuto);
    });
}

3.3 总结

我们到现在为止,如果想安全的调用CharacterController 中的方法,像让这些方法的执行条件都是CharacterController被初始化完成,那么就用_playerDecorator.ExecuteIfInitialized来调用方法。

通过装饰器无侵入的对CharacterController 的方法进行了是否被初始化的判断,初始化的判断和CharacterController完全解耦。

四、装饰器总结

4.1 装饰器模式是一种结构型设计模式,其核心目标是动态地给一个对象添加一些额外的职责,而无需修改其原始类。它通过创建一系列包装对象来实现功能的扩展

在例子中,我们通过装饰器对一个类加入了校验是否被初始化的职责。

4.2 装饰器模式也是一种组合代替继承的方式。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值