GeWuYou 96bf8a1982 refactor(ioc): 修改Architecture基类设计并更新文档
- 将Architecture基类从泛型改为非泛型设计,简化继承关系
- 移除泛型约束<T>,使架构定义更加简洁
- 更新GameArchitecture示例代码以适配新的基类设计
- 添加Contains、ContainsInstance和Clear等实用方法的详细文档
- 补充了容器实用方法的使用场景和注意事项说明
2026-01-12 14:43:40 +08:00

16 KiB
Raw Blame History

IoC 包使用说明

概述

IoCInversion of Control控制反转包提供了一个轻量级的依赖注入容器用于管理框架中各种组件的注册和获取。通过 IoC 容器,可以实现组件间的解耦,便于测试和维护。

核心类

IocContainer

IoC 容器类,负责管理对象的注册和获取。

主要功能:

  • 注册实例到容器
  • 从容器中获取实例
  • 类型安全的依赖管理
  • 线程安全操作
  • 容器冻结保护

核心方法

1. Register 和 Register(Type, object)

注册一个实例到容器中。

public void Register<T>(T instance)
public void Register(Type type, object instance)

参数:

  • instance: 要注册的实例对象
  • type: 要注册的类型(重载方法)

使用示例:

var container = new IocContainer();

// 注册各种类型的实例
container.Register<IPlayerModel>(new PlayerModel());
container.Register<IGameSystem>(new GameSystem());
container.Register<IStorageUtility>(new StorageUtility());

2. RegisterSingleton

注册单例实例到容器中。一个类型只允许一个实例。

public void RegisterSingleton<T>(T instance)

参数:

  • instance: 要注册的单例实例

使用示例:

var container = new IocContainer();

// 注册单例
container.RegisterSingleton<IPlayerModel>(new PlayerModel());

3. RegisterPlurality

注册多个实例,将实例注册到其实现的所有接口和具体类型上。

public void RegisterPlurality(object instance)

参数:

  • instance: 要注册的实例

4. RegisterSystem

注册系统实例,将其绑定到其所有实现的接口上。

public void RegisterSystem(ISystem system)

参数:

  • system: 系统实例对象

5. Get 和 GetAll

从容器中获取指定类型的实例。

public T? Get<T>() where T : class
public IReadOnlyList<T> GetAll<T>() where T : class

返回值:

  • Get<T>: 返回指定类型的实例,如果未找到则返回 null
  • GetAll<T>: 返回指定类型的所有实例列表,如果未找到则返回空数组

使用示例:

// 获取已注册的实例
var playerModel = container.Get<IPlayerModel>();
var gameSystems = container.GetAll<IGameSystem>();

// 如果类型未注册Get 返回 nullGetAll 返回空数组
var unknownService = container.Get<IUnknownService>();  // null

6. GetRequired

获取指定类型的必需实例,如果没有注册或注册了多个实例会抛出异常。

public T GetRequired<T>() where T : class

返回值:

  • 返回找到的唯一实例

7. GetAllSorted

获取并排序(系统调度专用)。

public IReadOnlyList<T> GetAllSorted<T>(Comparison<T> comparison) where T : class

参数:

  • comparison: 比较器委托,定义排序规则

返回值:

  • 按指定方式排序后的实例列表

在框架中的使用

Architecture 中的应用

IoC 容器是 Architecture 类的核心组件,用于管理所有的 System、Model 和 Utility。

public abstract class Architecture : IArchitecture
{
    // 内置 IoC 容器
    private readonly IocContainer _mContainer = new();

    // 注册系统
    public void RegisterSystem<TSystem>(TSystem system) where TSystem : ISystem
    {
        system.SetArchitecture(this);
        _mContainer.Register(system);  // 注册到容器
        // ...
    }

    // 获取系统
    public TSystem GetSystem<TSystem>() where TSystem : class, ISystem
        => _mContainer.Get<TSystem>();  // 从容器获取

    // Model 和 Utility 同理
}

注册组件到容器

public class GameArchitecture : Architecture
{
    protected override void Init()
    {
        // 这些方法内部都使用 IoC 容器

        // 注册 Model存储游戏数据
        RegisterModel<IPlayerModel>(new PlayerModel());
        RegisterModel<IInventoryModel>(new InventoryModel());

        // 注册 System业务逻辑
        RegisterSystem<IGameplaySystem>(new GameplaySystem());
        RegisterSystem<ISaveSystem>(new SaveSystem());

        // 注册 Utility工具类
        RegisterUtility<ITimeUtility>(new TimeUtility());
        RegisterUtility<IStorageUtility>(new StorageUtility());
    }
}

从容器获取组件

// 通过扩展方法间接使用 IoC 容器
public class PlayerController : IController
{
    public void Start()
    {
        // GetModel 内部调用 Architecture.GetModel
        // Architecture.GetModel 内部调用 IocContainer.Get
        var playerModel = this.GetModel<IPlayerModel>();
        
        var gameplaySystem = this.GetSystem<IGameplaySystem>();
        var timeUtility = this.GetUtility<ITimeUtility>();
    }
}

工作原理

内部实现

public class IocContainer
{
    // 使用字典存储类型到实例集合的映射
    private readonly Dictionary<Type, HashSet<object>> _typeIndex = new();
    private readonly HashSet<object> _objects = [];
    private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.NoRecursion);
    private volatile bool _frozen = false;
    
    public void Register<T>(T instance)
    {
        // 获取写锁以确保线程安全
        _lock.EnterWriteLock();
        try
        {
            RegisterInternal(typeof(T), instance!);
        }
        finally
        {
            _lock.ExitWriteLock();
        }
    }
    
    public T? Get<T>() where T : class
    {
        _lock.EnterReadLock();
        try
        {
            if (_typeIndex.TryGetValue(typeof(T), out var set) && set.Count > 0)
            {
                var result = set.First() as T;
                return result;
            }

            return null;
        }
        finally
        {
            _lock.ExitReadLock();
        }
    }

    private void RegisterInternal(Type type, object instance)
    {
        if (_frozen) throw new InvalidOperationException("IocContainer is frozen");

        _objects.Add(instance);

        if (!_typeIndex.TryGetValue(type, out var set))
        {
            set = [];
            _typeIndex[type] = set;
        }

        set.Add(instance);
    }
}

线程安全机制

容器使用 ReaderWriterLockSlim 来确保线程安全操作,允许多个线程同时读取,但在写入时阻止其他线程访问。

注册流程

用户代码
   ↓
RegisterSystem<T>(system)
   ↓
IocContainer.Register<T>(system)
   ↓
加写锁 -> Dictionary[typeof(T)] 添加实例到HashSet

获取流程

用户代码
   ↓
this.GetSystem<T>()
   ↓
Architecture.GetSystem<T>()
   ↓
IocContainer.Get<T>()
   ↓
加读锁 -> Dictionary.TryGetValue(typeof(T)) 获取HashSet
   ↓
返回HashSet中第一个实例或 null

使用示例

基础使用

// 1. 创建容器
var container = new IocContainer();

// 2. 注册服务
var playerService = new PlayerService();
container.Register<IPlayerService>(playerService);

// 3. 获取服务
var service = container.Get<IPlayerService>();
service.DoSomething();

接口和实现分离

// 定义接口
public interface IDataService
{
    void SaveData(string data);
    string LoadData();
}

// 实现类
public class LocalDataService : IDataService
{
    public void SaveData(string data) { /* 本地存储 */ }
    public string LoadData() { /* 本地加载 */ return ""; }
}

public class CloudDataService : IDataService
{
    public void SaveData(string data) { /* 云端存储 */ }
    public string LoadData() { /* 云端加载 */ return ""; }
}

// 注册(可以根据配置选择不同实现)
var container = new IocContainer();

#if CLOUD_SAVE
container.Register<IDataService>(new CloudDataService());
#else
container.Register<IDataService>(new LocalDataService());
#endif

// 使用(不需要关心具体实现)
var dataService = container.Get<IDataService>();
dataService.SaveData("game data");

注册多个实现

var container = new IocContainer();

// 注册多个相同接口的不同实现
container.Register<IDataService>(new LocalDataService());
container.Register<IDataService>(new CloudDataService());

// 获取单个实例(返回第一个)
var singleService = container.Get<IDataService>();  // 返回第一个注册的实例

// 获取所有实例
var allServices = container.GetAll<IDataService>();  // 返回两个实例的列表

其他实用方法

Contains<T>()

检查容器中是否包含指定类型的实例。

public bool Contains<T>() where T : class

参数:

  • 无泛型参数

返回值:

  • 如果容器中包含指定类型的实例则返回 true,否则返回 false

使用示例:

var container = new IocContainer();

// 检查服务是否已注册
if (container.Contains<IPlayerService>())
{
    Console.WriteLine("Player service is available");
}

// 根据检查结果决定是否注册
if (!container.Contains<ISettingsService>())
{
    container.Register<ISettingsService>(new SettingsService());
}

应用场景:

  • 条件注册服务
  • 检查依赖是否可用
  • 动态功能开关

ContainsInstance(object instance)

判断容器中是否包含某个具体的实例对象。

public bool ContainsInstance(object instance)

参数:

  • instance:待查询的实例对象

返回值:

  • 若容器中包含该实例则返回 true,否则返回 false

使用示例:

var container = new IocContainer();

var service = new MyService();
container.Register<IMyService>(service);

// 检查特定实例是否在容器中
if (container.ContainsInstance(service))
{
    Console.WriteLine("This instance is registered in the container");
}

// 检查另一个实例
var anotherService = new MyService();
if (!container.ContainsInstance(anotherService))
{
    Console.WriteLine("This instance is not in the container");
}

应用场景:

  • 避免重复注册同一实例
  • 检查对象是否已被管理
  • 调试和日志记录

Clear()

清空容器中的所有实例。

public void Clear()

使用示例:

var container = new IocContainer();

// 注册多个服务
container.Register<IService1>(new Service1());
container.Register<IService2>(new Service2());
container.Register<IService3>(new Service3());

// 清空容器
container.Clear();

// 检查是否清空成功
Console.WriteLine($"Contains IService1: {container.Contains<IService1>()}");  // False
Console.WriteLine($"Contains IService2: {container.Contains<IService2>()}");  // False

应用场景:

  • 重置容器状态
  • 内存清理
  • 测试环境准备

注意事项:

  • 容器冻结后也可以调用 Clear() 方法
  • 清空后,所有已注册的实例都将丢失
  • 不会自动清理已注册对象的其他引用

设计特点

1. 简单轻量

  • 支持多种注册方式:普通注册、单例注册、多实例注册
  • 基于字典和哈希集实现,性能高效
  • 无复杂的依赖解析逻辑

2. 手动注册

  • 需要显式注册每个组件
  • 不支持自动依赖注入
  • 完全可控的组件生命周期

3. 多实例支持

  • 每个类型可以注册多个实例
  • 提供 GetAll 方法获取所有实例
  • 提供 Get 方法获取单个实例

4. 类型安全

  • 基于泛型,编译时类型检查
  • 避免字符串键导致的错误
  • IDE 友好,支持自动补全

5. 线程安全

  • 使用读写锁确保多线程环境下的安全操作
  • 读操作可以并发执行
  • 写操作独占锁,防止并发修改冲突

6. 容器冻结

  • 提供 Freeze 方法,防止进一步修改容器内容
  • 防止在初始化后意外修改注册内容

与其他 IoC 容器的区别

本框架的 IocContainer

// 简单直接
var container = new IocContainer();
container.Register(new MyService());
var service = container.Get<MyService>();

特点:

  • 简单易用
  • 性能高
  • 线程安全
  • 支持多实例
  • 不支持构造函数注入
  • 不支持自动解析依赖
  • 不支持生命周期管理Transient/Scoped/Singleton

完整的 IoC 框架(如 Autofac、Zenject

// 复杂但功能强大
var builder = new ContainerBuilder();
builder.RegisterType<MyService>().As<IMyService>().SingleInstance();
builder.RegisterType<MyController>().WithParameter("config", config);
var container = builder.Build();

// 自动解析依赖
var controller = container.Resolve<MyController>();

特点:

  • 自动依赖注入
  • 生命周期管理
  • 复杂场景支持
  • 学习成本高
  • 性能开销大

最佳实践

1. 在架构初始化时注册

public class GameArchitecture : Architecture
{
    protected override void Init()
    {
        // 按顺序注册组件
        // 1. 工具类(无依赖)
        RegisterUtility(new TimeUtility());
        RegisterUtility(new StorageUtility());

        // 2. 模型(可能依赖工具)
        RegisterModel(new PlayerModel());
        RegisterModel(new GameModel());

        // 3. 系统(可能依赖模型和工具)
        RegisterSystem(new GameplaySystem());
        RegisterSystem(new SaveSystem());
    }
}

2. 使用接口类型注册

// ❌ 不推荐:直接使用实现类
RegisterSystem(new ConcreteSystem());
var system = GetSystem<ConcreteSystem>();

// ✅ 推荐:使用接口
RegisterSystem<IGameSystem>(new ConcreteSystem());
var system = GetSystem<IGameSystem>();

3. 避免运行时频繁注册

// ❌ 不好:游戏运行时频繁注册
void Update()
{
    RegisterService(new TempService());  // 每帧创建
}

// ✅ 好:在初始化时一次性注册
protected override void Init()
{
    RegisterService(new PersistentService());
}

4. 检查 null 返回值

// 获取可能不存在的服务
var service = container.Get<IOptionalService>();
if (service != null)
{
    service.DoSomething();
}
else
{
    GD.Print("Service not registered!");
}

5. 合理使用容器冻结

// 在架构初始化完成后冻结容器,防止意外修改
protected override void OnInit()
{
    // 注册所有组件
    RegisterModel(new PlayerModel());
    RegisterSystem(new GameSystem());
    // ...
    
    // 冻结容器
    Container.Freeze();  // 此后无法再注册新组件
}

注意事项

  1. 线程安全操作:容器内部使用读写锁确保线程安全,无需额外同步

  2. 容器冻结:一旦调用 Freeze 方法,将不能再注册新实例

  3. **单例注册限制 **RegisterSingleton 方法确保一个类型只能有一个实例,重复注册会抛出异常

  4. 内存管理:容器持有的实例不会自动释放,需要注意内存泄漏问题

  5. 注册顺序:组件的依赖关系需要手动保证,先注册被依赖的组件

相关包

  • architecture - 使用 IoC 容器管理所有组件
  • model - Model 通过 IoC 容器注册和获取
  • system - System 通过 IoC 容器注册和获取
  • utility - Utility 通过 IoC 容器注册和获取