gpt4 book ai didi

c# - 如何装饰依赖运行时值创建的类

转载 作者:太空狗 更新时间:2023-10-29 22:59:11 26 4
gpt4 key购买 nike

虽然我已经使用 Ninject 很长时间了,但我对使用 Simple Injector 还是个新手,所以总体上我对 DI 很满意。吸引我想使用 Simple Injector 的一件事是装饰器的易用性。

在请求服务时解决依赖关系的所有正常情况下,我已经能够成功地将装饰器与简单注入(inject)器一起使用。但是,我很难弄清楚是否有办法在必须使用运行时值构建服务的情况下应用我的装饰器。

在 Ninject 中,我可以传递 ConstructorArgumentkernel.Get<IService>请求可以沿着 N 个装饰器链一直继承到“真正的”实现类。我想不出使用 Simple Injector 复制它的方法。

我在下面放了一些非常基本的代码来说明。我想在现实世界中做的是传递 IMyClassFactory实例到我的应用程序中的其他类。然后其他类可以使用它来创建 IMyClass使用 IRuntimeValue 的实例他们会提供。 IMyClass他们从 IMyClassFactory 得到的实例将由注册的装饰器自动装饰。

我知道我可以在我的 IMyClassFactory 中手动应用我的装饰器或任何 Func<IMyClass>我可以想出办法,但我希望它“能正常工作”。

我一直在四处走动,试图抽象出 MyClass施工,但我不知道如何用 IRuntimeValue 解决它构造函数参数并被装饰。

我是否忽略了一个明显的解决方案?

using System;
using SimpleInjector;
using SimpleInjector.Extensions;

public class MyApp
{
[STAThread]
public static void Main()
{
var container = new Container();
container.Register<IMyClassFactory, MyClassFactory>();
container.RegisterDecorator(typeof (IMyClass), typeof (MyClassDecorator));

container.Register<Func<IRuntimeValue, IMyClass>>(
() => r => container.GetInstance<IMyClassFactory>().Create(r));

container.Register<IMyClass>(() => ?????)); // Don't know what to do

container.GetInstance<IMyClass>(); // Expect to get decorated class
}
}

public interface IRuntimeValue
{
}

public interface IMyClass
{
IRuntimeValue RuntimeValue { get; }
}

public interface IMyClassFactory
{
IMyClass Create(IRuntimeValue runtimeValue);
}

public class MyClassFactory : IMyClassFactory
{
public IMyClass Create(IRuntimeValue runtimeValue)
{
return new MyClass(runtimeValue);
}
}

public class MyClass : IMyClass
{
private readonly IRuntimeValue _runtimeValue;

public MyClass(IRuntimeValue runtimeValue)
{
_runtimeValue = runtimeValue;
}

public IRuntimeValue RuntimeValue
{
get
{
return _runtimeValue;
}
}
}

public class MyClassDecorator : IMyClass
{
private readonly IMyClass _inner;

public MyClassDecorator(IMyClass inner)
{
_inner = inner;
}

public IRuntimeValue RuntimeValue
{
get
{
return _inner.RuntimeValue;
}
}
}

编辑 1:

好的,感谢史蒂文的出色回答。它给了我一些想法。

也许让它更具体一点(虽然不是我的情况,更“经典”)。假设我有一个 ICustomer我在运行时通过读取数据库或从磁盘或其他东西反序列化创建的。所以我想引用 articles 中的一个将被视为“新的”史蒂文链接。我想创建一个 ICustomerViewModel 的实例这样我就可以显示和操作我的 ICustomer .我的混凝土CustomerViewModel类接受 ICustomer在其构造函数中以及另一个可以由容器解析的依赖项。

所以我有一个 ICustomerViewModelFactory有一个.Create(ICustomer customer)定义返回 ICustomerViewModel 的方法.在我问这个问题之前,我总是可以让它工作,因为在我执行 ICustomerViewModelFactory 时我可以这样做(在合成根中实现的工厂):

return new CustomerViewModel(customer, container.GetInstance<IDependency>());

我的问题是我想要我的 ICustomerViewModel由容器装饰并更新它绕过了它。现在我知道如何绕过这个限制了。

所以我想我的后续问题是:我的设计一开始就错了吗?我真的很喜欢ICustomer应该传递给 CustomerViewModel 的构造函数因为这表明它是必需的、得到验证等的意图。我不想在事后添加它。

最佳答案

Simple Injector 明确不支持通过 GetInstance 方法传递运行时值。这样做的原因是在构造对象图时不应使用运行时值。换句话说,你的 injectables 的构造函数不应依赖于运行时值。这样做有几个问题。首先,您的注入(inject)剂可能需要比那些运行时值长得多的生命周期。但也许更重要的是,您希望能够 verifydiagnose容器的配置,当您开始在对象图中使用运行时值时,这会变得更加麻烦。

所以一般来说有两种解决方案。您可以通过方法调用图传递运行时值,也可以创建可以在请求时提供此运行时值的“上下文”服务。

当您实践像 this 这样的架构时,通过调用图传递运行时值是一个特别有效的解决方案。和 this您通过系统传递消息的位置,或者运行时值可以成为服务契约(Contract)的明显部分的时间。在这种情况下,可以很容易地通过消息或方法传递运行时值,并且该运行时值也将通过任何装饰器传递。

在您的情况下,这意味着工厂创建了 IMyService 而没有传入 IRuntimeValue 并且您的代码将此值传递给 IMyService 使用它指定的方法:

var service = _myServiceFactory.Create();
service.DoYourThing(runtimeValue);

然而,通过调用图传递运行时值并不总是一个好的解决方案。特别是当此运行时值不应该是所发送消息的契约(Contract)的一部分时。这尤其适用于用作有关当前登录用户、当前系统时间等信息的上下文信息。您不想传递这些信息;您只是希望它可用。我们不希望这样,因为这会给每次都传递正确值的消费者带来额外的负担,而他们甚至可能不应该能够更改此信息(将用户置于执行请求的上下文中)实例)。

在这种情况下,您应该定义可以注入(inject)并允许检索此上下文的服务。例如:

public interface IUserContext {
User CurrentUser { get; }
}

public interface ITimeProvider {
DateTime Now { get; }
}

在这些情况下,当前用户和当前时间不会直接注入(inject)到构造函数中,而是这些服务。需要访问当前用户的组件可以简单地调用 _userContext.CurrentUser,这将在构造对象之后完成(阅读:在构造函数中)。因此:以一种懒惰的方式。

但这确实意味着 IRuntimeValue 必须在调用 MyClass 之前的某处设置。这可能意味着您需要在工厂内部进行设置。这是一个例子:

var container = new Container();
var context = new RuntimeValueContext();
container.RegisterSingle<RuntimeValueContext>(context);
container.Register<IMyClassFactory, MyClassFactory>();
container.RegisterDecorator(typeof(IMyClass), typeof(MyClassDecorator));
container.Register<IMyClass, MyClass>();

public class RuntimeValueContext {
private ThreadLocal<IRuntimeValue> _runtime;
public IRuntimeValue RuntimeValue {
get { return _runtime.Value; }
set { _runtime.Value = value; }
}
}

public class MyClassFactory : IMyClassFactory {
private readonly Container _container;
private readonly RuntimeValueContext context;
public MyClassFactory(Container container, RuntimeValueContext context) {
_container = container;
_context = context;
}

public IMyClass Create(IRuntimeValue runtimeValue) {
var instance = _container.GetInstance<IMyClass>();
_context.RuntimeValue = runtimeValue;
return instance;
}
}

public class MyClass : IMyClass {
private readonly RuntimeValueContext _context;
public MyClass(RuntimeValueContext context) {
_context = context;
}
public IRuntimeValue RuntimeValue { get { return _context.Value; } }
}

你也可以让MyClass接受IRuntimeValue并进行如下注册:

container.Register<IRuntimeValue>(() => context.Value);

但是不允许验证对象图,因为 Simple Injector 将确保注册永远不会返回 null,但 context.Value 默认为 null。所以另一种选择是执行以下操作:

container.Register<IMyClass>(() => new MyClass(context.Value));

这允许验证 IMyClass 注册,但在验证过程中仍会创建一个注入(inject)空值的新 MyClass 实例。如果在 MyClass 构造函数中有保护子句,这将失败。但是,此注册不允许容器自动连接 MyClass。例如,当您有更多依赖项要注入(inject) MyClass 时, Autowiring 该类会派上用场。

关于c# - 如何装饰依赖运行时值创建的类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22366190/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com