不可变类和统一应用程序块

发布于 2024-08-21 14:35:08 字数 1289 浏览 10 评论 0原文

我正在为自己编写一个小实用程序,因此当我当前使用 Unity 时,如果可以解决此问题,我可以更改为不同的 IoC 容器。

我想创建一个在创建时给出一些数据的类,但之后是不可变的,通常我会这样做:

class SomeItem
{
    public SomeItem(string name)
    {
        Name = name;
        Hash = new SomeHashingClass().GenerateHash(name);
    }

    public string Name { get; private set; }

    public string Hash { get; private set; }
}

问题是构造函数中对 SomeHashingClass 的依赖,如何在保持对象不可变的同时注入它?

一种方法是让构造函数仅采用依赖项,然后使用与当前构造函数相同的方法来完成实际工作。然而我讨厌这个想法,因为它可能会使对象处于存在但完全无效的状态。并且需要编写代码以确保该方法只能被调用一次。

我可以看到的另一种方法是创建一个统一解析的“SomeItemFactory”类,然后手动为 SomeItem 进行依赖注入,但这会使代码量增加一倍:

class SomeItemFactory
{
    IHashingClass _hashingClass;

    public SomeItemFactory(IHashingClass hashingClass)
    {
        _hashingClass = hashingClass;
    }

    public SomeItem Create(string name)
    {
        return new SomeItem(_hashingClass, name);
    }
}

class SomeItem
{
    public SomeItem(IHashingClass hashingClass, string name)
    {
        Name = name;
        Hash = hashingClass.GenerateHash(name);
    }

    public string Name { get; private set; }

    public string Hash { get; private set; }
}

请告诉我有一种干净的方法可以做到这一点。为什么没有这样的方法:

unityContainer.Resolve<SomeItem>("the items name");

I am writing a small utility for myself so whilst I am using Unity currently, I can change to a different IoC container if it will allow me to get around this problem.

I want to create a class that is given some data when it is created, but is immutable after that, normally I would do this with:

class SomeItem
{
    public SomeItem(string name)
    {
        Name = name;
        Hash = new SomeHashingClass().GenerateHash(name);
    }

    public string Name { get; private set; }

    public string Hash { get; private set; }
}

The problem is the dependency on SomeHashingClass in the constructor, how do I inject it whilst keeping the object immutable?

One way would be to have the constructor take just the dependency, then have a method which is the same as the current constructor to do the real work. However I hate that idea as it could leave the object in a state of existing but being totally invalid. And code would need to be written to make sure the method can only be called once.

The other way I can see to do it is to create a 'SomeItemFactory' class that unity resolves, then manually doing the dependency injection for SomeItem, however this doubles the amount of code:

class SomeItemFactory
{
    IHashingClass _hashingClass;

    public SomeItemFactory(IHashingClass hashingClass)
    {
        _hashingClass = hashingClass;
    }

    public SomeItem Create(string name)
    {
        return new SomeItem(_hashingClass, name);
    }
}

class SomeItem
{
    public SomeItem(IHashingClass hashingClass, string name)
    {
        Name = name;
        Hash = hashingClass.GenerateHash(name);
    }

    public string Name { get; private set; }

    public string Hash { get; private set; }
}

Please tell me there is a clean way to do this. Why is there no method like:

unityContainer.Resolve<SomeItem>("the items name");

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

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

发布评论

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

评论(3

策马西风 2024-08-28 14:35:08

虽然您确实可以将参数传递给 Resolve 方法,但请仔细考虑这是否确实是正确的设计。

你首先为什么要这样做?是因为您想使用 Unity 作为服务定位器吗?这确实是我能想到的唯一原因,但是 我认为这是一种反模式

DI 容器的使用应遵循好莱坞原则:告诉它 在组合根解析整个应用程序图,然后忘记它。

在您的特定情况下,您可以保持 SomeItem 不变,但如果您希望能够将 HashingClass 作为依赖项进行更改,则需要将其注入到 SomeItem 中,并且构造函数注入是最佳选择。

如果您的应用程序中只需要一个 SomeItem 实例,则可以像在 Unity 中那样将其连接起来,但如果您需要创建多个实例,则可以使用 抽象工厂是正确的方法

您的示例已经差不多了:您只需要从 SomeItemFactory 中提取一个接口,并在任何需要创建 SomeItem 实例的使用者中依赖此 ISomeItemFactory 。

它可能看起来像是更多的代码,但在任何情况下(无论哪种方式),代码行都不是衡量代码质量的特别好的指标。但是,这种方法允许您遵循单一职责原则并独立地改变 SomeItem 的创建和散列彼此的。

请注意,这些原则都不是专门针对 Unity 的,但适用广义上来说,DI 是通用的。

While you can, indeed, pass parameters to the Resolve method, consider carefully if this is actually the correct design.

Why do you want to do this in the first place? Is is because you want to use Unity as a Service Locator? That's really the only reason I can think of, but I consider this an anti-pattern.

Use of DI Containers should follow the Hollywood Principle: tell it to resolve the entire application graph at the Composition Root and then forget all about it.

In your particular case, you can either keep SomeItem as is, but if you want to be able to vary the HashingClass as a dependency, you need to inject it into SomeItem, and Constructor Injection is the best option.

If you only need a single SomeItem instance in your application, you can wire it up like that in Unity, but if you need to create several instances, an Abstract Factory is the correct approach.

Your examples are almost there: you just need to extract an interface from SomeItemFactory and take a dependency on this ISomeItemFactory in any consumer that needs to create SomeItem instances.

It may look like more code, but lines of code are not a particularly good metric of code quality in any case (one way or the other). However, this approach allows you to follow the Single Responsibility Principle and vary creation and hashing of SomeItem independently of each other.

Notice that none of these principles are directed specifically at Unity, but applies broadly to DI in general.

难得心□动 2024-08-28 14:35:08

看来你可以将参数传递给 unity 的 Resolve() 方法。有关详细信息,请参阅此问题已接受的答案: 我可以传递构造函数吗Unity 的 Resolve() 方法的参数?

It seems you can pass parameters to unity's Resolve() method. See the accepted answer of this question for details: Can I pass constructor parameters to Unity's Resolve() method?

玻璃人 2024-08-28 14:35:08

可以按照你在配置中所说的那样做

unityContainer.Resolve<ISomeInterface>("MyMappingName");

,假设你正在使用配置文件,你可以有

<typeAliases>
    <typeAlias alias="SomeInterface" type="ISomeInterface, SomeAssembly" />
    <typeAlias alias="SomeConcreteType" type="SomeType, SomeAssembly" />
</typeAliases>

<containers>
    <container>
        <type type="SomeInterface" mapTo="SomeConcreteType" name="MyMappingName" />
    </container>
</containers>

It is possible to do as you say in unity

unityContainer.Resolve<ISomeInterface>("MyMappingName");

In config, assuming you are using a config file, you can have

<typeAliases>
    <typeAlias alias="SomeInterface" type="ISomeInterface, SomeAssembly" />
    <typeAlias alias="SomeConcreteType" type="SomeType, SomeAssembly" />
</typeAliases>

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