使用服务定位而不是构造函数注入来避免编写工厂类的负载是否不好

发布于 2024-08-08 15:30:10 字数 1550 浏览 8 评论 0原文

现在我们使用 DI/IOC,当我们需要将额外的参数传递给构造函数时,我们使用工厂类,例如

public class EmailSender 
{
    internal EmailSender(string toEmail, string subject,String body, ILogger emailLogger)
    {.....} 
}

public class EmailSenderFactory
{
    ILogger emailLogger;
    public EmailSenderFactory(ILogger emailLogger)
    { 
        this.emailLogger = emailLogger;
    }
    public EmailSender Create(string toEmail, string subject, string body) 
    {
        return new EmailSender(toEmail, subject, body, emailLogger);
    }
}

现在的问题是我们最终创建了很多工厂类,而人们并不总是知道如何使用他们(有时他们自己更新它们)。像这样编写类的最大缺点是什么:

public class EmailSender 
{
    EmailLogger logger = IoC.Resolve<ILogger>();
    internal EmailSender(string toEmail, string subject,String body)
    {.....} 
}

优点:我们现在可以安全地使用构造函数,而不需要工厂类 缺点:我们必须引用服务定位器(我不担心可测试性,它很容易使用模拟容器作为容器的支持服务)。

难道有什么大的理由让我们不应该这样做吗?

编辑:经过一番思考,我发现通过拥有一个私有构造函数并嵌套 Factory 类,我可以将实现和工厂保持在一起,并防止人们不正确地创建类,所以问题变得有点没有实际意义。所有关于 SL 很脏的观点当然都是正确的,所以下面的解决方案让我很高兴:

public class EmailSender 
{
    public class Factory
    {
        ILogger emailLogger;
        public Factory(ILogger emailLogger)
        { 
            this.emailLogger = emailLogger;
        }
        public EmailSender Create(string toEmail, string subject, string body) 
        {
            return new EmailSender(toEmail, subject, body, emailLogger);
        }
    }
    private EmailSender(string toEmail, string subject,String body, ILogger emailLogger)
    {
    } 
}

Right now we use DI/IOC and when we need to pass extra parameters to a constructor we use a factory class e.g.

public class EmailSender 
{
    internal EmailSender(string toEmail, string subject,String body, ILogger emailLogger)
    {.....} 
}

public class EmailSenderFactory
{
    ILogger emailLogger;
    public EmailSenderFactory(ILogger emailLogger)
    { 
        this.emailLogger = emailLogger;
    }
    public EmailSender Create(string toEmail, string subject, string body) 
    {
        return new EmailSender(toEmail, subject, body, emailLogger);
    }
}

Now the problem with this is that we end up creating a whole lotta factory classes, and people don't always know to use them (they sometimes new them up themselves). What are the biggest negatives of coding the class like this:

public class EmailSender 
{
    EmailLogger logger = IoC.Resolve<ILogger>();
    internal EmailSender(string toEmail, string subject,String body)
    {.....} 
}

Pro: we now can use the constructor safely without needing a factory class
Con: we have to reference the Service Locator (I'm not worried about testability, its easy to use a mock container as the backing service for the container).

Is there some big stinker of a reason out there why we shouldn't do this?

edit: after a bit of thought, I twigged that by having a private constructor, and by nesting the Factory class, I could keep the implementation and factory together, and prevent people from creating classes improperly, so the question has become somewhat moot. All the points points about SL being dirty, are of course true, so the solution below keeps me happy:

public class EmailSender 
{
    public class Factory
    {
        ILogger emailLogger;
        public Factory(ILogger emailLogger)
        { 
            this.emailLogger = emailLogger;
        }
        public EmailSender Create(string toEmail, string subject, string body) 
        {
            return new EmailSender(toEmail, subject, body, emailLogger);
        }
    }
    private EmailSender(string toEmail, string subject,String body, ILogger emailLogger)
    {
    } 
}

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

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

发布评论

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

评论(3

污味仙女 2024-08-15 15:30:10

是的——这很糟糕。

  • 当框架可以完成工作时,为什么还要编写所有代码呢?所有 IoC.Resolve() 调用都是
    多余的,你不应该这样做
    写下它们。
  • 另一个甚至更重要的方面,
    是你的组件绑定到
    您的服务定位器。

    您现在无法实例化它们
    就像那样 - 你需要一个
    完全设置服务定位器
    每次需要使用时放置

  • 最后但最重要的是 - 您的 SL 代码是
    遍布你的代码库
    这不是一件好事,因为
    当你想要改变某事时
    你必须在多个地方寻找。

yes - it is bad.

  • Why write all that code when you can have the framework do the work? All the IoC.Resolve() calls are
    superfluous and you shouldn't have to
    write them.
  • Another, even more important aspect,
    is that your components are tied to
    your service locator.

    You're now unable to instantiate them
    just like that - you need a
    completely set up service locator in
    place every time you need to use a
    component.

  • Last but, bot least - your SL code is
    sprinkled all over your codebase
    which is not a good thing, because
    when you want to change something,
    you have to look in multiple places.
过期以后 2024-08-15 15:30:10

我能想到的最大原因(不只考虑一般服务定位器的问题)是,这不是我作为您的班级的用户所会的期望

元讨论:
某些 DI 框架(例如 Guice)将 为您构建工厂

有些人主张将“新的”来自“可注射的”。

The largest reason I can think of (without just looking at issues with Service Locators in general) is that it is not what I as a user of your class would expect.

Meta discussion:
Some DI frameworks (Guice for example) will build the factory for you.

Some people advocate separating the "newable" from the "injectable".

臻嫒无言 2024-08-15 15:30:10

我不太确定克日什托夫给出的这个强烈的“这很糟糕”的答案。我认为其中存在一些权衡和偏好,但没有绝对将它们归类为坏或好。

  • 我认为编写这些 IoC.Resolve() 调用并不比为注入机制编写特定的构造函数或属性更多余。
  • 对于这一点,我必须同意您与服务定位器绑定在一起,并且必须在实例化类之前对其进行设置。然而:
    • 您可以将服务定位器与更具体的接口分开。从而减少系统中每个服务与庞大服务定位器的耦合
    • 是的,如果你使用 DI 机制,你将删除所有那些 IoC.Resolve(),但你仍然需要使用一种容器来实例化你的“主要”服务。 DI 必须拦截这些调用,不是吗?
    • 您的服务定位器可以(应该?)“自动配置”,或者至少非常容易设置。
  • 请参阅上面的“将服务定位器与更具体的接口隔离...”点。

我认为使用服务定位器确实将您的依赖项隐藏在类中,而不是通过构造函数公开它们。在我看来,这很不方便,因为直到在未配置的情况下调用服务定位器之前,您不会知道您的类缺少某些内容。

但 DI 并没有摆脱这种代码黑暗。当您使用 DI 时,理解这些依赖项如何在您的构造函数中“出现”(DI 魔法)确实并不明显。通过使用 SL,您至少可以看到这些依赖项来自何处。

但是,当测试一个公开其构造函数的依赖项的类时,您(几乎)不会错过它。使用服务定位器时情况并非如此。

我并不是说克日什托夫错了,因为我最同意他的观点。但我很确定使用服务定位器并不一定是一个糟糕的“设计”,而且肯定不仅仅是糟糕。

菲尔

I'm not quite sure about this strong "it is bad" answer gave by Krzysztof. I think there's some trade-off and preference in there without absolutely categorizing them as bad or good.

  • I don't think it is more superfluous writing those IoC.Resolve() call than writing specific constructors or properties for the injection mechanism.
  • This one, i have to agree that you are tied to a service Locator and you have to set it up before instancing a class. However:
    • You can segregate your service locator with more specific interfaces. Thereby reducing the coupling with a huge service locator for every services of your system
    • Yeah, if you use DI mechanism, you will remove all those IoC.Resolve(), but you would still have to use a kind of container to instantiate your "main" services. The DI has to intercept those calls, no?
    • Your service locator could (should?) be "auto-configurable" or at least, pretty easy to set up.
  • See above the "segregate your service locator with more specific interfaces..." point.

I think using a service locator is indeed hiding your dependencies inside the class instead of exposing them through constructors. And it is an inconvenient in my opinion because you will not know your class is missing something until the service locator is called without being configured.

But the DI thing is not free of that kind of code darkness. When you use DI, it is really not obvious to understand how those dependencies just "appeared" (DI magic) in your constructor. By using a SL, you at least can see where those dependencies are coming from.

But still, when testing a class that expose those dependencies on her constructors, you (almost) can't miss it. That is not the case using a service locator.

I'm not saying Krzysztof was wrong, because I agree with him for the most. But I'm pretty sure using a service locator is not necessarily a bad "design" and certainly not simply bad.

Phil

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