From 16fae83f703cd71648493bf5626a6ae4c85a5fc4 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Sun, 5 Apr 2026 10:03:48 +0800 Subject: [PATCH] =?UTF-8?q?feat(architectures):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=9E=B6=E6=9E=84=E5=9F=BA=E7=B1=BB=E5=92=8C=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96=E7=BC=96=E6=8E=92=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现 Architecture 基类,提供系统、模型、工具等组件的注册与管理功能 - 添加 ArchitectureBootstrapper 协调器,负责初始化期间的基础设施准备工作 - 实现生命周期管理、组件注册管理和模块管理功能 - 提供同步和异步初始化方法,支持不同场景下的架构初始化需求 - 添加架构上下文绑定和 IOC 容器冻结功能,确保运行时依赖图稳定 - 实现架构生命周期钩子注册和阶段变更事件功能 - 添加完整的单元测试验证初始化编排流程的正确性 --- ...ArchitectureInitializationPipelineTests.cs | 188 ++++++++++++++++++ GFramework.Core/Architectures/Architecture.cs | 71 ++----- .../Architectures/ArchitectureBootstrapper.cs | 118 +++++++++++ 3 files changed, 326 insertions(+), 51 deletions(-) create mode 100644 GFramework.Core.Tests/Architectures/ArchitectureInitializationPipelineTests.cs create mode 100644 GFramework.Core/Architectures/ArchitectureBootstrapper.cs diff --git a/GFramework.Core.Tests/Architectures/ArchitectureInitializationPipelineTests.cs b/GFramework.Core.Tests/Architectures/ArchitectureInitializationPipelineTests.cs new file mode 100644 index 00000000..717e1b18 --- /dev/null +++ b/GFramework.Core.Tests/Architectures/ArchitectureInitializationPipelineTests.cs @@ -0,0 +1,188 @@ +using GFramework.Core.Abstractions.Enums; +using GFramework.Core.Abstractions.Events; +using GFramework.Core.Architectures; +using GFramework.Core.Environment; +using GFramework.Core.Logging; +using Microsoft.Extensions.DependencyInjection; + +namespace GFramework.Core.Tests.Architectures; + +/// +/// 验证 初始化编排流程的单元测试。 +/// 这些测试覆盖环境初始化、服务准备、上下文绑定和自定义服务配置的时序, +/// 以确保核心协调器在拆分后仍保持既有行为。 +/// +[TestFixture] +public class ArchitectureInitializationPipelineTests +{ + /// + /// 为每个测试准备独立的日志工厂和游戏上下文状态。 + /// + [SetUp] + public void SetUp() + { + LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider(); + GameContext.Clear(); + } + + /// + /// 清理测试期间注册的全局游戏上下文,避免跨测试污染。 + /// + [TearDown] + public void TearDown() + { + GameContext.Clear(); + } + + /// + /// 验证异步初始化会在执行用户初始化逻辑之前准备环境、服务和上下文。 + /// + [Test] + public async Task InitializeAsync_Should_Prepare_Runtime_Before_OnInitialize() + { + var environment = new TrackingEnvironment(); + var marker = new BootstrapMarker(); + var architecture = new InitializationPipelineTestArchitecture(environment, marker); + + await architecture.InitializeAsync(); + + AssertRuntimePrepared(architecture, environment, marker); + await architecture.DestroyAsync(); + } + + /// + /// 验证同步初始化路径复用同一套基础设施准备流程。 + /// + [Test] + public async Task Initialize_Should_Prepare_Runtime_Before_OnInitialize() + { + var environment = new TrackingEnvironment(); + var marker = new BootstrapMarker(); + var architecture = new InitializationPipelineTestArchitecture(environment, marker); + + architecture.Initialize(); + + AssertRuntimePrepared(architecture, environment, marker); + await architecture.DestroyAsync(); + } + + /// + /// 断言初始化阶段所需的运行时准备工作都已经完成。 + /// + /// 待验证的测试架构实例。 + /// 测试使用的环境对象。 + /// 通过服务配置委托注册的标记服务。 + private static void AssertRuntimePrepared( + InitializationPipelineTestArchitecture architecture, + TrackingEnvironment environment, + BootstrapMarker marker) + { + Assert.Multiple(() => + { + Assert.That(architecture.ObservedEnvironmentInitialized, Is.True); + Assert.That(architecture.ObservedConfiguredServiceAvailable, Is.True); + Assert.That(architecture.ObservedEventBusAvailable, Is.True); + Assert.That(architecture.ObservedContextWasBound, Is.True); + Assert.That(architecture.ObservedEnvironmentRegistered, Is.True); + Assert.That(architecture.Context.GetEnvironment(), Is.SameAs(environment)); + Assert.That(architecture.Context.GetService(), Is.SameAs(marker)); + Assert.That(architecture.CurrentPhase, Is.EqualTo(ArchitecturePhase.Ready)); + }); + } + + /// + /// 跟踪初始化期间关键可观察状态的测试架构。 + /// + private sealed class InitializationPipelineTestArchitecture : Architecture + { + private readonly TrackingEnvironment _environment; + private readonly BootstrapMarker _marker; + + /// + /// 使用可观察环境和标记服务创建测试架构。 + /// + /// 用于验证初始化时序的环境对象。 + /// 用于验证服务钩子执行结果的标记服务。 + public InitializationPipelineTestArchitecture( + TrackingEnvironment environment, + BootstrapMarker marker) + : base(environment: environment) + { + _environment = environment; + _marker = marker; + } + + /// + /// 记录用户初始化逻辑执行时环境是否已经准备完成。 + /// + public bool ObservedEnvironmentInitialized { get; private set; } + + /// + /// 记录自定义服务是否已在用户初始化前注册到容器。 + /// + public bool ObservedConfiguredServiceAvailable { get; private set; } + + /// + /// 记录内置事件总线是否已在用户初始化前可用。 + /// + public bool ObservedEventBusAvailable { get; private set; } + + /// + /// 记录当前上下文是否已在用户初始化前绑定到全局游戏上下文表。 + /// + public bool ObservedContextWasBound { get; private set; } + + /// + /// 记录环境对象是否已在用户初始化前注册到架构上下文。 + /// + public bool ObservedEnvironmentRegistered { get; private set; } + + /// + /// 为容器注册测试标记服务,用于验证初始化前的服务钩子是否执行。 + /// + public override Action? Configurator => services => services.AddSingleton(_marker); + + /// + /// 在用户初始化逻辑中采集运行时准备状态。 + /// + protected override void OnInitialize() + { + ObservedEnvironmentInitialized = _environment.InitializeCallCount == 1; + ObservedConfiguredServiceAvailable = ReferenceEquals(Context.GetService(), _marker); + ObservedEventBusAvailable = Context.GetService() is not null; + ObservedContextWasBound = ReferenceEquals(GameContext.GetByType(GetType()), Context); + ObservedEnvironmentRegistered = ReferenceEquals(Context.GetEnvironment(), _environment); + } + } + + /// + /// 用于验证环境初始化是否发生的测试环境。 + /// + private sealed class TrackingEnvironment : EnvironmentBase + { + /// + /// 获取测试环境名称。 + /// + public override string Name { get; } = "Tracking"; + + /// + /// 获取环境初始化调用次数。 + /// + public int InitializeCallCount { get; private set; } + + /// + /// 记录环境初始化次数。 + /// + public override void Initialize() + { + InitializeCallCount++; + } + } + + /// + /// 通过服务配置委托注册到容器的测试标记对象。 + /// + private sealed class BootstrapMarker + { + } +} \ No newline at end of file diff --git a/GFramework.Core/Architectures/Architecture.cs b/GFramework.Core/Architectures/Architecture.cs index c3e47436..cabffb0e 100644 --- a/GFramework.Core/Architectures/Architecture.cs +++ b/GFramework.Core/Architectures/Architecture.cs @@ -37,19 +37,25 @@ public abstract class Architecture : IArchitecture IArchitectureServices? services = null, IArchitectureContext? context = null) { - Configuration = configuration ?? new ArchitectureConfiguration(); - Environment = environment ?? new DefaultEnvironment(); - Services = services ?? new ArchitectureServices(); + var resolvedConfiguration = configuration ?? new ArchitectureConfiguration(); + var resolvedEnvironment = environment ?? new DefaultEnvironment(); + var resolvedServices = services ?? new ArchitectureServices(); _context = context; // 初始化 Logger - LoggerFactoryResolver.Provider = Configuration.LoggerProperties.LoggerFactoryProvider; + LoggerFactoryResolver.Provider = resolvedConfiguration.LoggerProperties.LoggerFactoryProvider; _logger = LoggerFactoryResolver.Provider.CreateLogger(GetType().Name); // 初始化管理器 - _lifecycle = new ArchitectureLifecycle(this, Configuration, Services, _logger); - _componentRegistry = new ArchitectureComponentRegistry(this, Configuration, Services, _lifecycle, _logger); - _modules = new ArchitectureModules(this, Services, _logger); + _bootstrapper = new ArchitectureBootstrapper(GetType(), resolvedEnvironment, resolvedServices, _logger); + _lifecycle = new ArchitectureLifecycle(this, resolvedConfiguration, resolvedServices, _logger); + _componentRegistry = new ArchitectureComponentRegistry( + this, + resolvedConfiguration, + resolvedServices, + _lifecycle, + _logger); + _modules = new ArchitectureModules(this, resolvedServices, _logger); } #endregion @@ -70,21 +76,6 @@ public abstract class Architecture : IArchitecture #region Properties - /// - /// 获取架构配置对象 - /// - private IArchitectureConfiguration Configuration { get; } - - /// - /// 获取环境配置对象 - /// - private IEnvironment Environment { get; } - - /// - /// 获取服务管理器 - /// - private IArchitectureServices Services { get; } - /// /// 当前架构的阶段 /// @@ -129,6 +120,11 @@ public abstract class Architecture : IArchitecture /// private IArchitectureContext? _context; + /// + /// 初始化基础设施编排器 + /// + private readonly ArchitectureBootstrapper _bootstrapper; + /// /// 生命周期管理器 /// @@ -284,32 +280,7 @@ public abstract class Architecture : IArchitecture /// 是否启用异步模式 private async Task InitializeInternalAsync(bool asyncMode) { - // === 基础环境初始化 === - Environment.Initialize(); - - // 注册内置服务模块 - Services.ModuleManager.RegisterBuiltInModules(Services.Container); - - // 将 Environment 注册到容器 - if (!Services.Container.Contains()) - Services.Container.RegisterPlurality(Environment); - - // 初始化架构上下文 - _context ??= new ArchitectureContext(Services.Container); - GameContext.Bind(GetType(), _context); - - // 为服务设置上下文 - Services.SetContext(_context); - if (Configurator is null) - { - _logger.Debug("Mediator-based cqrs will not take effect without the service setter configured!"); - } - - // 执行服务钩子 - Services.Container.ExecuteServicesHook(Configurator); - - // 初始化服务模块 - await Services.ModuleManager.InitializeAllAsync(asyncMode); + _context = await _bootstrapper.PrepareForInitializationAsync(_context, Configurator, asyncMode); // === 用户 OnInitialize === _logger.Debug("Calling user OnInitialize()"); @@ -320,9 +291,7 @@ public abstract class Architecture : IArchitecture await _lifecycle.InitializeAllComponentsAsync(asyncMode); // === 初始化完成阶段 === - Services.Container.Freeze(); - _logger.Info("IOC container frozen"); - + _bootstrapper.CompleteInitialization(); _lifecycle.MarkAsReady(); _logger.Info($"Architecture {GetType().Name} is ready - all components initialized"); } diff --git a/GFramework.Core/Architectures/ArchitectureBootstrapper.cs b/GFramework.Core/Architectures/ArchitectureBootstrapper.cs new file mode 100644 index 00000000..984c0a07 --- /dev/null +++ b/GFramework.Core/Architectures/ArchitectureBootstrapper.cs @@ -0,0 +1,118 @@ +using GFramework.Core.Abstractions.Architectures; +using GFramework.Core.Abstractions.Environment; +using GFramework.Core.Abstractions.Logging; +using Microsoft.Extensions.DependencyInjection; + +namespace GFramework.Core.Architectures; + +/// +/// 协调架构初始化期间的基础设施准备工作。 +/// 该类型将环境初始化、服务模块启动、上下文绑定和服务容器配置从 中拆出, +/// 使核心架构类只保留生命周期入口和公共 API 协调职责。 +/// +internal sealed class ArchitectureBootstrapper( + Type architectureType, + IEnvironment environment, + IArchitectureServices services, + ILogger logger) +{ + /// + /// 在执行用户 OnInitialize 之前准备架构运行时。 + /// 该流程必须保证环境、内置服务、上下文和服务钩子已经可用, + /// 因为用户初始化逻辑通常会立即访问事件总线、查询执行器或环境对象。 + /// + /// 调用方已经提供的上下文;如果为空则创建默认上下文。 + /// 可选的容器配置委托,用于接入 Mediator 等扩展服务。 + /// 是否以异步模式初始化服务模块。 + /// 已绑定到当前架构类型的架构上下文。 + public async Task PrepareForInitializationAsync( + IArchitectureContext? existingContext, + Action? configurator, + bool asyncMode) + { + InitializeEnvironment(); + RegisterBuiltInModules(); + EnsureEnvironmentRegistered(); + + var context = EnsureContext(existingContext); + ConfigureServices(context, configurator); + await InitializeServiceModulesAsync(asyncMode); + return context; + } + + /// + /// 完成用户组件初始化之后的收尾工作。 + /// 冻结容器可以阻止 Ready 阶段之后的意外服务注册,保持运行时依赖图稳定。 + /// + public void CompleteInitialization() + { + services.Container.Freeze(); + logger.Info("IOC container frozen"); + } + + /// + /// 初始化运行环境,使环境对象在后续服务构建和用户初始化前进入可用状态。 + /// + private void InitializeEnvironment() + { + environment.Initialize(); + } + + /// + /// 注册框架内置服务模块。 + /// 该步骤必须先于执行服务钩子,以便容器具备 CQRS 和事件总线等基础服务。 + /// + private void RegisterBuiltInModules() + { + services.ModuleManager.RegisterBuiltInModules(services.Container); + } + + /// + /// 确保环境对象可以通过架构容器解析。 + /// 如果调用方已经预先注册了自定义环境实例,则保留现有绑定,避免覆盖外部配置。 + /// + private void EnsureEnvironmentRegistered() + { + if (!services.Container.Contains()) + services.Container.RegisterPlurality(environment); + } + + /// + /// 获取本次初始化使用的架构上下文,并将其绑定到全局游戏上下文表。 + /// 绑定发生在用户初始化之前,确保组件在注册阶段即可通过架构类型解析上下文。 + /// + /// 外部提供的上下文。 + /// 实际用于本次初始化的上下文实例。 + private IArchitectureContext EnsureContext(IArchitectureContext? existingContext) + { + var context = existingContext ?? new ArchitectureContext(services.Container); + GameContext.Bind(architectureType, context); + return context; + } + + /// + /// 为服务容器设置上下文并执行扩展配置钩子。 + /// 这一步统一承接 Mediator 等容器扩展的接入点,避免 直接操作容器细节。 + /// + /// 当前架构上下文。 + /// 可选的服务集合配置委托。 + private void ConfigureServices(IArchitectureContext context, Action? configurator) + { + services.SetContext(context); + + if (configurator is null) + logger.Debug("Mediator-based cqrs will not take effect without the service setter configured!"); + + services.Container.ExecuteServicesHook(configurator); + } + + /// + /// 初始化所有服务模块。 + /// 该过程在用户注册系统、模型和工具之前完成,避免组件在初始化期间访问未准备好的服务。 + /// + /// 是否允许异步初始化服务模块。 + private async Task InitializeServiceModulesAsync(bool asyncMode) + { + await services.ModuleManager.InitializeAllAsync(asyncMode); + } +} \ No newline at end of file