一、从一个需求开始
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 装饰器模式也是一种组合代替继承的方式。

1651

被折叠的 条评论
为什么被折叠?



