是否存在初始化通过 DI 容器创建的对象的模式

发布于 2024-08-15 19:43:19 字数 808 浏览 5 评论 0原文

我正在尝试让 Unity 来管理对象的创建,并且我想要一些直到运行时才知道的初始化参数:

目前我能想到的唯一方法是使用 Init接口上的方法。

interface IMyIntf {
  void Initialize(string runTimeParam);
  string RunTimeParam { get; }
}

然后要使用它(在 Unity 中),我会这样做:

var IMyIntf = unityContainer.Resolve<IMyIntf>();
IMyIntf.Initialize("somevalue");

在这种情况下,runTimeParam 参数是在运行时根据用户输入确定的。这里的简单情况只是返回 runTimeParam 的值,但实际上参数将类似于文件名,并且初始化方法将对文件执行某些操作。

这会产生许多问题,即 Initialize 方法在接口上可用并且可以被多次调用。在实现中设置一个标志并在重复调用 Initialize 时抛出异常似乎很笨拙。

在解析接口时,我不想了解有关 IMyIntf 实现的任何信息。不过,我真正想要的是知道该接口需要某些一次性初始化参数。有没有办法以某种方式用这些信息注释(属性?)接口,并在创建对象时将这些信息传递给框架?

编辑:对界面进行了更多描述。

I am trying to get Unity to manage the creation of my objects and I want to have some initialization parameters that are not known until run-time:

At the moment the only way I could think of the way to do it is to have an Init method on the interface.

interface IMyIntf {
  void Initialize(string runTimeParam);
  string RunTimeParam { get; }
}

Then to use it (in Unity) I would do this:

var IMyIntf = unityContainer.Resolve<IMyIntf>();
IMyIntf.Initialize("somevalue");

In this scenario runTimeParam param is determined at run-time based on user input. The trivial case here simply returns the value of runTimeParam but in reality the parameter will be something like file name and initialize method will do something with the file.

This creates a number of issues, namely that the Initialize method is available on the interface and can be called multiple times. Setting a flag in the implementation and throwing exception on repeated call to Initialize seems way clunky.

At the point where I resolve my interface I don't want to know anything about the implementation of IMyIntf. What I do want, though, is the knowledge that this interface needs certain one time initialization parameters. Is there a way to somehow annotate(attributes?) the interface with this information and pass those to framework when the object is created?

Edit: Described the interface a bit more.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

深海蓝天 2024-08-22 19:43:19

任何需要运行时值来构建特定依赖项的地方,抽象工厂都是解决方案。

在接口上使用初始化方法有点泄漏抽象的味道。

在您的情况下,我想说您应该根据您需要如何使用它来对 IMyIntf 接口进行建模 - 而不是您打算如何创建其实现。这是一个实现细节。

因此,接口应该简单地是:

public interface IMyIntf
{
    string RunTimeParam { get; }
}

现在定义抽象工厂:

public interface IMyIntfFactory
{
    IMyIntf Create(string runTimeParam);
}

您现在可以创建 IMyIntfFactory 的具体实现,它创建 IMyIntf 的具体实例,如下所示:

public class MyIntf : IMyIntf
{
    private readonly string runTimeParam;

    public MyIntf(string runTimeParam)
    {
        if(runTimeParam == null)
        {
            throw new ArgumentNullException("runTimeParam");
        }

        this.runTimeParam = runTimeParam;
    }

    public string RunTimeParam
    {
        get { return this.runTimeParam; }
    }
}

注意这是如何实现的允许我们通过使用readonly关键字来保护类的不变量。不需要有异味的初始化方法。

IMyIntfFactory 实现可能像这样简单:

public class MyIntfFactory : IMyIntfFactory
{
    public IMyIntf Create(string runTimeParam)
    {
        return new MyIntf(runTimeParam);
    }
}

在需要 IMyIntf 实例的所有消费者中,您只需通过以下方式依赖于 IMyIntfFactory通过构造函数注入请求它。

如果您正确注册,任何有价值的 DI 容器都将能够为您自动连接 IMyIntfFactory 实例。

Any place where you need a run-time value to construct a particular dependency, Abstract Factory is the solution.

Having Initialize methods on the interfaces smells of a Leaky Abstraction.

In your case I would say that you should model the IMyIntf interface on how you need to use it - not how you intent to create implementations thereof. That's an implementation detail.

Thus, the interface should simply be:

public interface IMyIntf
{
    string RunTimeParam { get; }
}

Now define the Abstract Factory:

public interface IMyIntfFactory
{
    IMyIntf Create(string runTimeParam);
}

You can now create a concrete implementation of IMyIntfFactory that creates concrete instances of IMyIntf like this one:

public class MyIntf : IMyIntf
{
    private readonly string runTimeParam;

    public MyIntf(string runTimeParam)
    {
        if(runTimeParam == null)
        {
            throw new ArgumentNullException("runTimeParam");
        }

        this.runTimeParam = runTimeParam;
    }

    public string RunTimeParam
    {
        get { return this.runTimeParam; }
    }
}

Notice how this allows us to protect the class' invariants by use of the readonly keyword. No smelly Initialize methods are necessary.

An IMyIntfFactory implementation may be as simple as this:

public class MyIntfFactory : IMyIntfFactory
{
    public IMyIntf Create(string runTimeParam)
    {
        return new MyIntf(runTimeParam);
    }
}

In all your consumers where you need an IMyIntf instance, you simply take a dependency on IMyIntfFactory by requesting it through Constructor Injection.

Any DI Container worth its salt will be able to auto-wire an IMyIntfFactory instance for you if you register it correctly.

行雁书 2024-08-22 19:43:19

通常,当您遇到这种情况时,您需要重新审视您的设计并确定是否将有状态/数据对象与纯服务混合在一起。在大多数(不是全部)情况下,您需要将这两种类型的对象分开。

如果您确实需要在构造函数中传递特定于上下文的参数,一种选择是创建一个工厂,通过构造函数解决您的服务依赖关系,并将运行时参数作为 Create() 方法(或Generate( )、Build() 或任何您为工厂方法命名的名称)。

使用 setter 或 Initialize() 方法通常被认为是糟糕的设计,因为您需要“记住”调用它们并确保它们不会打开太多的实现状态(即什么是阻止某人重新调用初始化或设置器?)。

Usually when you encounter this situation, you need to revisit your design and determine if you are mixing your stateful/data objects with your pure services. In most (not all) cases, you will want to keep these two types of objects separate.

If you do need a context-specific parameter passed in the constructor, one option is to create a factory that resolves your service dependencies via the constructor, and takes your run-time parameter as a parameter of the Create() method (or Generate(), Build() or whatever you name your factory methods).

Having setters or an Initialize() method are generally thought to be bad design, as you need to "remember" to call them and make sure they don't open up too much of your implementation's state (i.e. what is to stop someone from re-calling initialize or the setter?).

很快妥协 2024-08-22 19:43:19

我在基于 Model 对象动态创建 ViewModel 对象的环境中也遇到过几次这种情况(其他 Stackoverflow 帖子)。

我喜欢 Ninject 扩展,它允许您基于接口动态创建工厂:

<代码>
Bind().ToFactory();

我无法直接在 Unity 中找到任何类似的功能;因此,我为 IUnityContainer 编写了自己的扩展,它允许您注册工厂,这些工厂将根据现有对象的数据创建新对象,本质上是从一种类型层次结构映射到不同类型层次结构: UnityMappingFactory@GitHub

出于简单性和可读性的目标,我最终得到了一个扩展,它允许您直接指定映射,而无需声明单独的工厂类或接口(真正节省时间)。您只需在正常引导过程中注册类的位置添加映射...

//make sure to register the output...
container.RegisterType<IImageWidgetViewModel, ImageWidgetViewModel>();
container.RegisterType<ITextWidgetViewModel, TextWidgetViewModel>();

//define the mapping between different class hierarchies...
container.RegisterFactory<IWidget, IWidgetViewModel>()
.AddMap<IImageWidget, IImageWidgetViewModel>()
.AddMap<ITextWidget, ITextWidgetViewModel>();

然后您只需在 CI 的构造函数中声明映射工厂接口并使用其 Create() 方法

public ImageWidgetViewModel(IImageWidget widget, IAnotherDependency d) { }

public TextWidgetViewModel(ITextWidget widget) { }

public ContainerViewModel(object data, IFactory<IWidget, IWidgetViewModel> factory)
{
    IList<IWidgetViewModel> children = new List<IWidgetViewModel>();
    foreach (IWidget w in data.Widgets)
        children.Add(factory.Create(w));
}

...额外的好处是,映射类的构造函数中的任何其他依赖项也将在对象创建期间得到解决。

显然,这并不能解决所有问题,但到目前为止它对我很有帮助,所以我认为我应该分享它。 GitHub 上该项目的网站上有更多文档。

I also have come across this situation a few times in environments where I am dynamically creating ViewModel objects based on Model objects (outlined really well by this other Stackoverflow post).

I liked how the Ninject extension which allows you to dynamically create factories based on interfaces:


Bind<IMyFactory>().ToFactory();

I could not find any similar functionality directly in Unity; so I wrote my own extension to the IUnityContainer which allows you to register factories that will create new objects based on data from existing objects essentially mapping from one type hierarchy to a different type hierarchy: UnityMappingFactory@GitHub

With a goal of simplicity and readability, I ended up with an extension that allows you to directly specify the mappings without declaring individual factory classes or interfaces (a real time saver). You just add the mappings right where you register the classes during the normal bootstrapping process...

//make sure to register the output...
container.RegisterType<IImageWidgetViewModel, ImageWidgetViewModel>();
container.RegisterType<ITextWidgetViewModel, TextWidgetViewModel>();

//define the mapping between different class hierarchies...
container.RegisterFactory<IWidget, IWidgetViewModel>()
.AddMap<IImageWidget, IImageWidgetViewModel>()
.AddMap<ITextWidget, ITextWidgetViewModel>();

Then you just declare the mapping factory interface in the constructor for CI and use its Create() method...

public ImageWidgetViewModel(IImageWidget widget, IAnotherDependency d) { }

public TextWidgetViewModel(ITextWidget widget) { }

public ContainerViewModel(object data, IFactory<IWidget, IWidgetViewModel> factory)
{
    IList<IWidgetViewModel> children = new List<IWidgetViewModel>();
    foreach (IWidget w in data.Widgets)
        children.Add(factory.Create(w));
}

As an added bonus, any additional dependencies in the constructor of the mapped classes will also get resolved during object creation.

Obviously, this won't solve every problem but it has served me pretty well so far so I thought I should share it. There is more documentation on the project's site on GitHub.

呆萌少年 2024-08-22 19:43:19

我无法用特定的 Unity 术语来回答,但听起来您只是在学习依赖项注入。如果是这样,我强烈建议您阅读简短、清晰且信息丰富的 Ninject 用户指南

这将引导您了解使用 DI 时的各种选项,以及如何解决在此过程中遇到的具体问题。在您的情况下,您很可能希望使用 DI 容器来实例化您的对象,并让该对象通过构造函数获取对其每个依赖项的引用。

本演练还详细介绍了如何使用属性来注释方法、属性甚至参数,以在运行时区分它们。

即使您不使用 Ninject,本演练也会为您提供适合您目的的功能的概念和术语,并且您应该能够将这些知识映射到 Unity 或其他 DI 框架(或说服您尝试一下 Ninject) 。

I can't answer with specific Unity terminology but it sounds like you are just learning about dependency injection. If so, I urge you to read the brief, clear, and information packed user guide for Ninject.

This will walk you through the various option you have when using DI, and how to account for the specific issues that you'll face along the way. In your case, you would most likely want to use the DI container to instantiate your objects, and have that object get a reference to each of its dependencies through the constructor.

The walkthrough also details how to annotate methods, properties, and even parameters using attributes to distinguish them at runtime.

Even if you do not use Ninject, the walkthrough will give you the concepts and terminology of the functionality that suits your purpose, and you should be able to map that knowledge to Unity or other DI frameworks (or convince yout to give Ninject a try).

会傲 2024-08-22 19:43:19

我想我解决了它,而且感觉相当健康,所以它一定是对了一半:))

我将 IMyIntf 分成了“getter”和“setter”接口。因此:

interface IMyIntf {
  string RunTimeParam { get; }
}


interface IMyIntfSetter {
  void Initialize(string runTimeParam);
  IMyIntf MyIntf {get; }
}

然后,实现:

class MyIntfImpl : IMyIntf, IMyIntfSetter {
  string _runTimeParam;

  void Initialize(string runTimeParam) {
    _runTimeParam = runTimeParam;
  }

  string RunTimeParam { get; }

  IMyIntf MyIntf {get {return this;} }
}

//Unity configuration:
//Only the setter is mapped to the implementation.
container.RegisterType<IMyIntfSetter, MyIntfImpl>();
//To retrieve an instance of IMyIntf:
//1. create the setter
IMyIntfSetter setter = container.Resolve<IMyIntfSetter>();
//2. Init it
setter.Initialize("someparam");
//3. Use the IMyIntf accessor
IMyIntf intf = setter.MyIntf;

IMyIntfSetter.Initialize() 仍然可以多次调用,但使用 服务定位器范例 我们可以很好地包装它,以便 IMyIntfSetter 几乎是一个与 IMyIntf 不同的内部接口。

I think I solved it and it feels rather wholesome, so it must be half right :))

I split IMyIntf into a "getter" and a "setter" interfaces. So:

interface IMyIntf {
  string RunTimeParam { get; }
}


interface IMyIntfSetter {
  void Initialize(string runTimeParam);
  IMyIntf MyIntf {get; }
}

Then the implementation:

class MyIntfImpl : IMyIntf, IMyIntfSetter {
  string _runTimeParam;

  void Initialize(string runTimeParam) {
    _runTimeParam = runTimeParam;
  }

  string RunTimeParam { get; }

  IMyIntf MyIntf {get {return this;} }
}

//Unity configuration:
//Only the setter is mapped to the implementation.
container.RegisterType<IMyIntfSetter, MyIntfImpl>();
//To retrieve an instance of IMyIntf:
//1. create the setter
IMyIntfSetter setter = container.Resolve<IMyIntfSetter>();
//2. Init it
setter.Initialize("someparam");
//3. Use the IMyIntf accessor
IMyIntf intf = setter.MyIntf;

IMyIntfSetter.Initialize() can still be called multiple times but using bits of Service Locator paradigm we can wrap it up quite nicely so that IMyIntfSetter is almost an internal interface that is distinct from IMyIntf.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文