You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用SimpleInjector解析含运行时参数的实例并绑定Buffer?

问题描述

我的程序中有大量需在运行时创建且至少包含一个参数的对象,存在多层嵌套类,其中新计算的数据为后续内部实例所必需。

结构说明

  1. 用户发送字符串请求 ->
  2. 程序确定IRequestContext和ICommand ->
  3. 调用ICommand.Execute(IRequestContext) ->
  4. 执行过程通过工厂创建IStep[]和IPlan(IStep[])并发送至调度器 ->
  5. 调度器在指定时间结合IRequestContext启动IPlan ->
  6. IPlan通过调用IObservable<Report> IStep.Make(IRequestContext)逐步执行 ->
  7. IStep的实现包含Buffer实例,用于在当前Plan范围内共享执行结果,它不返回但对生成最终Report及其他用途是必需的,且Buffer无法定义为接口,本质是数据存储 ->
  8. Report包含enum Result和IMessageBuilder,IMessageBuilder仅包含一个Build()方法。

细节补充

  • 每个类自身依赖诸多接口(如数据库操作、本地化、通信API接口),这些依赖已在组合根注册,不存在问题。
  • IRequestContext实例为每个用户请求在运行时创建,IStep、IMessageBuilder及各类Buffer均依赖它。因此无法直接注册SomeMessageBuilder,因为构建器所需数据在步骤执行过程中生成,且不同请求不能共享同一MessageBuilder或Buffer实例。
  • 每个IMessageBuilder的实现所需参数数量和类型不同,因此无法修改接口让IMessageBuilder.Build(Entity1, Entity2, ...)接收这些实体,不得不为每个IMessageBuilder实现创建并注册工厂,导致每个IStep实现依赖IFactory<T1,T2,...,IMessageBuilder>。
  • 每个IPlan实现都有专属工厂来创建步骤的独特组合,此外还使用了步骤的组合模式实现(如CompositeStep(IStep[] step) : IStep),因此不得不为每个IStep实现注册工厂,还要为不同步骤组成的每个CompositeStep注册工厂。

简化示例

(注:有数十个不同的IPlan实现,它们可能使用相同的步骤)

核心接口与类

public interface IPlan<in TContext,out TReport>{
  IObservable<TReport> Execute(TContext context);
  bool IsReadyToGoNext(TReport report);
}
public interface IStep<in TContext,out TReport>{
  IObservable<TReport> Make(TContext context);
}
public interface IFactory<in T1, out TObject>
{
  TObject Create(T1 param);
}

public interface IMessageBuilder{
  SendMessage Build();
}
public class CreateMessageBuilder : LocalizedMessageBuilder, IMessageBuilder
{
  public CreateMessageBuilder(long chatId, CultureInfo info
  , ILocalizationProvider localizationProvider, int minSymbols, int maxSymbols)
  {...}
  public void SetUser(UserRepresentation representation){...};
  public void SetSirena(SirenRepresentation sirena){...};
  public void IsUserAllowedToCreateSirena(bool isAllowed){...};
  public void IsTitleValid(bool isValid){...};
  public override SendMessage Build(){...};
}

Plan工厂示例

public class CreateSirenaPlanFactory : IFactory<IRequestContext,CommandPlan>
{
  private readonly Container container;
  private readonly IFactory<IRequestContext, CreateMessageBuilder> messageBuilderFactory;
  private readonly IFactory<CreateMessageBuilder, CreateSirenaStep.Buffer> bufferFactory;

  public CreateSirenaPlanFactory(Container container
  , IFactory<IRequestContext, CreateMessageBuilder> messageBuilderFactory
  , IFactory<CreateMessageBuilder,CreateSirenaStep.Buffer>  bufferFactory)
  
  {
    this.container = container;
    this.messageBuilderFactory = messageBuilderFactory;
    this.bufferFactory = bufferFactory;
  }

  public IPlan Create(IRequestContext context)
  {
    CreateMessageBuilder messageBuilder = messageBuilderFactory.Create(context);
    CreateSirenaStep.Buffer buffer = bufferFactory.Create(messageBuilder);

    // 同一Buffer实例需在以下步骤组间共享
    // 但每个新请求都要创建新的Plan和Buffer实例
    var validation = new CompositeCommandStep([
      container.GetInstance<CheckAbilityToCreateSirenaStep>(),
      container.GetInstance<ValidateTitleCreateSirenaStep>()
    ]);
    
    IStep<IRequestContext,CommandStep.Report>[] steps = [
      container.GetInstance<GetUserCreateSirenaStep>(),
      validation,
      container.GetInstance<RequestDBToCreateSirenaStep>(),
    ];
    return new CommandPlan(CreateSirenaCommand.NAME,steps);
  }
}

核心问题

如何完成这些绑定?我不想为每个依赖一个或多个运行时生成参数的IStep实现创建工厂,且因SimpleInjector无上下文概念,完全不知道该如何绑定Buffer。

补充说明:曾使用Unity引擎的Zenject/Extenject,它有更多划分绑定上下文、为特定工厂的实例创建条件绑定的选项,可惜SimpleInjector缺乏这类工具。原本可为每个Plan实例创建上下文,每个上下文对应一个Buffer实例,且无需为每个需参数的实例创建工厂,只需绑定原生带参工厂。令我意外的是SimpleInjector文档仅提及作用域未提及上下文,而我无法在作用域中创建Buffer实例,因为作用域会在Create方法结束时关闭,实例会被释放。或许可使用流动作用域,但仍不清楚如何解析带参数的实例。


解决方案

针对SimpleInjector的特性,可通过以下几种方式解决你的问题,避免大量手动创建工厂,同时处理Buffer的上下文共享:

1. 利用SimpleInjector自动工厂替代手动IFactory实现

SimpleInjector支持自动解析Func<TArg, TInstance>类型依赖,无需手动实现IFactory<T1, TObject>接口。

比如对于CreateMessageBuilder,先在组合根注册其依赖的服务,再注册一个工厂委托,从IRequestContext提取运行时参数:

// 先注册固定依赖
container.Register<ILocalizationProvider>(Lifestyle.Singleton);

// 注册自动工厂委托
container.RegisterInstance<Func<IRequestContext, CreateMessageBuilder>>(context =>
    new CreateMessageBuilder(
        context.ChatId,
        context.CultureInfo,
        container.GetInstance<ILocalizationProvider>(),
        context.Settings.MinSymbols,
        context.Settings.MaxSymbols));

之后其他类直接依赖Func<IRequestContext, CreateMessageBuilder>即可创建实例,无需手动编写工厂类。

2. 使用流动作用域管理Plan级Buffer生命周期

流动作用域可让Buffer在Plan创建到执行完成的整个周期内保持存活,避免作用域提前关闭的问题。

步骤:

  1. 在组合根配置默认作用域为异步流动作用域,并注册Buffer:
container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
container.Register<CreateSirenaStep.Buffer>(Lifestyle.Scoped);
  1. 在Plan工厂的Create方法中启动流动作用域,解析Buffer并关联MessageBuilder:
public IPlan Create(IRequestContext context)
{
    var scope = AsyncScopedLifestyle.BeginScope(container);
    
    // 从当前作用域解析Buffer,后续步骤共享同一实例
    var buffer = scope.GetInstance<CreateSirenaStep.Buffer>();
    var messageBuilder = scope.GetInstance<Func<IRequestContext, CreateMessageBuilder>>()(context);
    buffer.MessageBuilder = messageBuilder;

    // 解析步骤时,自动从流动作用域获取Buffer
    var validation = new CompositeCommandStep([
        scope.GetInstance<CheckAbilityToCreateSirenaStep>(),
        scope.GetInstance<ValidateTitleCreateSirenaStep>()
    ]);
    
    var steps = new IStep<IRequestContext, CommandStep.Report>[]
    {
        scope.GetInstance<GetUserCreateSirenaStep>(),
        validation,
        scope.GetInstance<RequestDBToCreateSirenaStep>(),
    };
    
    // 将作用域与Plan绑定,执行时确保作用域激活
    var plan = new CommandPlan(CreateSirenaCommand.NAME, steps)
    {
        ExecutionScope = scope
    };
    
    return plan;
}
  1. 在IPlan.Execute方法执行前,检查并激活绑定的作用域,执行完成后再释放。

3. 用装饰器模式自动为步骤注入Buffer

为需要Buffer的步骤创建装饰器,自动注入当前作用域内的Buffer实例,无需手动传递:

// 标记接口,让需要Buffer的步骤实现
public interface IBufferAwareStep<TBuffer>
{
    void SetBuffer(TBuffer buffer);
}

// 步骤装饰器
public class StepBufferDecorator<TContext, TReport, TBuffer> : IStep<TContext, TReport>
{
    private readonly IStep<TContext, TReport> _decoratedStep;
    private readonly TBuffer _buffer;

    public StepBufferDecorator(IStep<TContext, TReport> decoratedStep, TBuffer buffer)
    {
        _decoratedStep = decoratedStep;
        _buffer = buffer;
    }

    public IObservable<TReport> Make(TContext context)
    {
        if (_decoratedStep is IBufferAwareStep<TBuffer> bufferAware)
        {
            bufferAware.SetBuffer(_buffer);
        }
        return _decoratedStep.Make(context);
    }
}

在组合根为特定步骤注册装饰器:

container.RegisterDecorator(
    typeof(IStep<,>),
    typeof(StepBufferDecorator<,,>).MakeGenericType(
        typeof(IRequestContext), typeof(CommandStep.Report), typeof(CreateSirenaStep.Buffer)),
    context => context.ImplementationType == typeof(CheckAbilityToCreateSirenaStep));

这样步骤被解析时会自动获取对应的Buffer实例。

4. 自定义执行上下文传递共享实例

创建一个PlanExecutionContext类,封装IRequestContext、Buffer、MessageBuilder等共享实例,在Plan创建时初始化,步骤执行时统一传递:

public class PlanExecutionContext
{
    public IRequestContext RequestContext { get; }
    public CreateSirenaStep.Buffer Buffer { get; }
    public IMessageBuilder MessageBuilder { get; }

    public PlanExecutionContext(IRequestContext requestContext, CreateSirenaStep.Buffer buffer, IMessageBuilder messageBuilder)
    {
        RequestContext = requestContext;
        Buffer = buffer;
        MessageBuilder = messageBuilder;
    }
}

// 修改IStep接口
public interface IStep<out TReport>{
  IObservable<TReport> Make(PlanExecutionContext context);
}

Plan工厂只需初始化这个上下文,步骤直接从上下文获取所需实例,无需依赖工厂。


内容的提问来源于stack exchange,提问作者HedgehogNSK

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 00:24:50