【问题标题】:How to pass state into a dependency chain using dependency injection如何使用依赖注入将状态传递到依赖链中
【发布时间】:2018-01-08 01:26:18
【问题描述】:

我在依赖链中有许多服务类(服务 A 依赖于服务 B,服务 B 依赖于服务 C 等);它们的行为由一个公共参数(CountryCode)决定,可能支持的国家是在运行时定义的。

注意:Actor 可以扩展为多个实例(不同的线程),并且一个事件只会由单个 Actor 处理,下面的服务是瞬态的(尽管如果需要我可以考虑更改它)。

目前我有这样的事情:

//This application flow starts off with this class
public class ActorExample
{
    private IServiceOne _serviceOne; //Has dependent service

    public async Task ProcessAsync(Event event)
    {
        //This value needs to be passed to _serviceOne and any children
        //but we only know its value at runtime.
        event.CountryCode; 
    }
}

public class ServiceOne : IServiceOne
{
    private IServiceTwo _serviceTwo; //Has another nested dependency

    //Implementation here varies depending on event.CountryCode
    public async Task DoSomething()
}

public class ServiceTwo : IServiceTwo
{
    //Implementation here varies depending on event.CountryCode
    public async Task DoSomething()
}

我想我或许可以将泛型与服务一起使用,因此像这样传递国家/地区代码:

public class ServiceTwo<TCountryCode> : IServiceTwo<TCountryCode> 

但是因为我们只有在运行时才有这个值,所以这是不可能的,尤其是在注入服务时。

另一种解决方案是将依赖 CountryCode 的服务注入为 null,然后在构造函数中填充,如下所示:

container.Register(Component.For<IActor>().ImplementedBy<Actor>()
         .DependsOn(Dependency.OnValue("CountryCode", null));

但是这看起来很麻烦而且很麻烦,尤其是在嵌套很深的时候。

如果一切都失败了,我可能会考虑在调用函数之前设置商店,但我必须为每个函数都这样做,例如:

_serviceOne.SetCountry(CountryCode).DoSomething();

注意:我们将城堡用于 IOC

【问题讨论】:

    标签: c# dependency-injection castle-windsor actor chain


    【解决方案1】:

    这可以通过使用类型化工厂来实现:

    public interface IServiceOneFactory
    {
        IServiceOne Create(CountryCode countryCode);
    }
    
    public class ServiceOne : IServiceOne
    {
        public ServiceOne(IServiceTwo servicetwo, CountryCode countryCode)
        {
        }
    }
    
    public class ActorExample
    {
        private raedonly IServiceOneFactory factory;
    
        public ActorExample(IServiceOneFactory factory)
        {
            this.factory = factory;
        }
    
        public async Task ProcessAsync(Event event)
        {
            var serviceOne = this.factory.Create(event.CountryCode);
        }
    }
    

    ServiceTwo 也一样...

    以及注册:

    kernel.AddFacility<TypedFactoryFacility>();
    kernel.Register(Component.For<IServiceOneFactory>().AsFactory();
    kernel.Register(Component.For<IServiceOne>().ImplementedBy<ServiceOne>());
    kernel.Register(Component.For<IServiceTwo>().ImplementedBy<ServiceTwo>());
    kernel.Register(Component.For<IActor>().ImplementedBy<Actor>());
    

    更多信息在这里:

    https://github.com/castleproject/Windsor/blob/master/docs/typed-factory-facility-interface-based.md

    编辑: 为避免代码重复,您可以创建通用工厂:

    public interface IServiceFactory<T>
    {
        T Create(CountryCode countryCode);
    }
    
    public ActorExample(IServiceFactory<IServiceOne> factory)
    {
        this.factory = factory;
    }
    
    kernel.Register(Component.For(typeof(IServiceFactory<>)).AsFactory();
    

    【讨论】:

    • 感谢您的回复,我对此解决方案的问题是我的服务将与工厂(1 对 1)耦合,这意味着每次我有服务时,我都必须创建一个工厂。我目前有 9 种不同的服务(我可以看到这个数字在增长)。这会给代码库增加很多工厂噪音。
    • 然后不是在构造函数中包含 CountryCode,而是通过方法调用 DoSomething() 传递它。
    • 是的,这是我最后的手段,我希望有另一种优雅的方式来处理这个问题。
    • 从您的代码示例来看,这似乎是一种最好的方法,但如果您仍然需要在构造函数中有一个参数,并且您希望减少工厂数量而不是创建通用工厂。
    • 能否请您扩展创建通用工厂?我确实试了一下,但因为我们只知道 CountryCode 类型在运行时使用泛型加上依赖注入在我看来是不可能的。
    【解决方案2】:

    由于国家代码是运行时数据,因此您应该将其作为参数传递给您的服务方法。

    如果这是不可接受的,即更改方法签名以包含国家/地区代码没有意义(例如,从概念的角度来看),那么您可以将国家/地区代码存储在 State Holder(其唯一责任的对象是知道“当前”国家代码)并且让需要知道“当前”国家代码的对象可以访问(通过依赖)这个状态持有者。

    本文的“控制共享状态:状态持有者模式”部分通过示例详细说明了此模式:http://www.dotnetcurry.com/patterns-practices/1367/data-encapsulation-large-csharp-applications

    【讨论】:

    • 感谢您的回复,保持状态是可能的。但是,参与者将收到 x 个包含不同 CountryCodes 的事件。然后我必须将它存储在某种状态机中,并在事件被处理后以某种方式管理它的删除(这似乎很棘手)。我担心这个解决方案可能会打开另一罐蠕虫。
    • @FishFingers,你能在问题中提供相关细节吗?例如单个请求(从 A 到 B 到 C...的调用)是否在单个线程上运行?
    • @FishFingers,如果单个事件将由单个线程一直处理,那么您需要做的就是创建线程本地的状态持有者模式的实现。您可以使用 .NET 框架中的 ThreadLocal 类。
    • @YacoubMassad 您已经考虑了一个 非常重要 点:在运行时设置的变量肯定会在某个时候为 NULL(无论您是否将其包装到容器中或不)。周围没有意义。因此,程序员有责任确保在消费者调用它之前首先设置这个变量。在使用 DI 框架时,这是您必须接受的权衡,因为正常的构造函数链会强制您指定时间链,这保证了“先发生”的关系
    • @cobby,我用特殊类型包装状态,以便在状态对象不包含任何值(例如 NULL)时收到可读的异常消息,这表明存在编程错误。我不确定我是否理解如何使用 DI 框架来解决这个问题。你能详细说明一下吗?
    猜你喜欢
    • 2023-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-18
    • 2017-05-02
    • 2021-07-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多