简体   繁体   English

服务定位器是框架的好模式吗?

[英]Is Service Locator a good pattern for a framework?

I'm building a GUI framework and I currently have a lot of lines like (usually in the constructor) 我正在构建一个GUI框架,目前有很多类似(通常在构造函数中)的行

var renderer = locator.GetService<IRenderer>();
var input = locator.GetService<IMouseInput>();

Those services I'm getting are core services, I mean, services that are inherently part of what is considered the basics . 我得到的那些服务是核心服务,我的意思是,这些服务本质上是被视为基础知识的一部分

I have thought about using constructor injection, but some annoyances arise. 我已经考虑过使用构造函数注入,但是会出现一些烦恼。

  • I will have to modify almost every constructor to have its dependencies injected, and that means it will receive even the most basic "services". 我将不得不修改几乎每个构造函数以注入其依赖项,这意味着它甚至将获得最基本的“服务”。 I'm afraid of having 3 or 4 arguments in each constructor. 我担心每个构造函数中都有3或4个参数。
  • But another thing that is even worse is that end users of the framework won't be happy if they don't find parameterless constructors . 但是更糟糕的是,如果框架的最终用户找不到无参数的构造器,他们将会感到不满意。 Do you imagine that creating a TextBox required you to pass all the dependencies it really needs? 您是否认为创建TextBox要求您传递它真正需要的所有依赖关系?

The end user could be overwhelmed with new TextBox(dep1, dep2, dep3) . 最终用户可能会被new TextBox(dep1, dep2, dep3)

So, is there a good way to remove the Service Locator completely without making it too complex? 因此,有没有一种好的方法可以完全删除服务定位器而又不会使其变得过于复杂?

EDIT 编辑

In the team we are discussing and they still WANT to keep the Service Locator arguing the following. 在团队中,我们正在讨论,他们仍然希望让Service Locator争论以下问题。 This is a fragment of the conversation: 这是对话的一部分:

We don't care about single resposibility in TextBox implementation. 我们不在乎TextBox实现中的单一可重复性。 We arent' writing an application. 我们没有写申请书。 It's a UI framework! 这是一个UI框架! It's supposed to hide stuff and details from end user, not to expose them. 它应该向最终用户隐藏东西和细节,而不是暴露它们。 A fluent builder for a TextBox? 流利的TextBox构建器? Seriously? 真的吗? We aren't writing some web request handling pipeline. 我们没有编写一些Web请求处理管道。

I agree with all what he (Stack Overflow users) wrote, but regarding applications . 我同意他(Stack Overflow用户)写的所有内容,但是关于应用程序 Where you want stuff to be as extensible and reusable as possible. 您希望东西尽可能地可扩展和可重用的地方。 We have like 10 services. 我们有10项服务。 They are used everywhere . 它们无处不在 If we had like 50 dependencies with complex object graphs, we couldn't do anything without DI and stuff. 如果我们对复杂的对象图有50个依赖关系,那么没有DI和东西就什么也做不了。 The thing about DI is that you have pay for it. 关于DI的事情是您已经为此付出了代价。 So for us Service Locator is free DI isn't It would be just unmaintainable- By having more complex API, by having backward API/ABI compatibility, etc. We are paying for Service Locator by not knowing what class needs as its dependencies and some problems with providing different dependency implementations for different instances. 因此,对我们而言,Service Locator是免费的DI并不是无法维护的-通过具有更复杂的API,具有向后的API / ABI兼容性等。我们为Service Locator付出了代价,因为它不知道需要什么类作为其依赖项以及一些为不同的实例提供不同的依赖实现的问题。 But we don't need either, so Service Locator is free. 但是我们也不需要,因此服务定位器是免费的。 DI isn't. DI不是。

Do you agree? 你同意吗? Why? 为什么?

I will have to modify almost every constructor to have its dependencies injected, and that means it will receive even the most basic "services". 我将不得不修改几乎每个构造函数以注入其依赖项,这意味着它甚至将获得最基本的“服务”。 I'm afraid of having 4 or 5 arguments in each constructor. 我担心每个构造函数中都有4或5个参数。

If you have 4 or 5 services in a constructor, it is an indication you are violating the Single Responsibility Principle . 如果构造函数中有4或5个服务,则表明您违反了“ 单一职责原则” At that point it is time to refactor to an Aggregate Service (otherwise known as a facade service) . 到那时,是时候重构为聚合服务(也称为外观服务)了

But another thing that is even worse is that end users of the framework will not have parameterless constructors. 但是,更糟糕的是,框架的最终用户将没有无参数的构造函数。 Do you imagine that creating a TextBox required you you pass all the dependencies it really needs? 您是否想象过创建TextBox需要您传递它真正需要的所有依赖关系?

Well, first of all it should be clear whether you are designing a framework or in fact that it is a library . 好吧,首先应该清楚您是在设计框架还是实际上是一个

But either way, you should make it possible to inject services for overriding, but provide logical default behaviors. 但是无论哪种方式,您都应该可以注入用于覆盖的服务,但要提供逻辑上的默认行为。 The best way to achieve that is by using a fluent builder to compose your services. 最好的方法是使用流利的构建器来组合您的服务。 That is, the public API will be comprised of builders that will do all of the configuration of the underlying services. 也就是说,公共API将由构建器组成,这些构建器将完成基础服务的所有配置。 You can then expose overloads that allow injection of custom services as required by the end user (ie one overload to build a default set of services with a builder, and one overload that just accepts the abstraction). 然后,您可以公开允许最终用户根据需要注入自定义服务的重载(即,一个重载可以使用构建器构建一组默认服务,而一个重载仅接受抽象)。

Here is a quick example that shows how that can be done: 这是一个简单的示例,显示了如何完成此操作:

public class VideoContentBuilder : IVideoContentBuilder
{
    private readonly string thumbnailLocation;
    private readonly string title;
    private readonly ICompressor compressor;

    public VideoContentBuilder()
        // Supply logical defaults
        : this(
            thumbnailLocation: string.Empty,
            title: string.Empty,
            compressor: new DefaultVideoCompressor(new Dependency())
        )
    {}

    private VideoContentBuilder(
        string thumbnailLocation,
        string title,
        ICompressor compressor)
    {
        this.thumbnailLocation = thumbnailLocation;
        this.title = title;
        this.compressor = compressor;
    }

    public IVideoContentBuilder WithThumbnailLocation(string thumbnailLocation)
    {
        return new VideoContentBuilder(thumbnailLocation, this.title, this.compressor);
    }

    public IVideoContentBuilder WithTitle(string title)
    {
        return new VideoContentBuilder(this.thumbnailLocation, title, this.compressor);
    }

    // Use a builder to configure defualt services
    //
    // Syntax:
    // .WithCompressor(compressor => compressor.WithLevel(Level.Maximum).WithEncryption(Encryption.None))
    public IVideoContentBuilder WithCompressor(Func<ICompressorBuilder, ICompressorBuilder> expression)
    {
        var starter = new CompressorBuilder(this.compressor);
        var builder = expression(starter);
        var compressor = builder.Create();

        return new VideoContentBuilder(this.thumbnailLocation, this.title, compressor);
    }

    // Allow a custom compressor to be injected.
    //
    // Syntax:
    // .WithCompressor(new CustomCompressor())
    public IVideoContentBuilder WithCompressor(ICompressor compressor)
    {
        return new VideoContentBuilder(this.thumbnailLocation, this.title, compressor);
    }

    // Create the configured service.
    public IVideoContent Create()
    {
        return new VideoContent(this.thumbnailLocation, this.title, compressor);
    }
)

Usage 用法

var videoContentBuilder = new VideoContentBuilder()
    .WithThumbnailLocation("http://www.example.com/thumb.jpg")
    .WithTitle("The greatest video")
    .WithCompressor(compressor => compressor
        .WithQuality(Level.High)
        .WithAlgorithm(Algorithm.ReallyCool)
        .WithDependentService(new DependentService())
    );

var videoContent = videoContentBuilder.Create();

References: 参考文献:

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM