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

本文关键字:模式 定位器 框架 服务 | 更新日期: 2023-09-27 18:12:00

我正在构建一个GUI框架,我目前有很多这样的行(通常在构造函数中)

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

我得到的这些服务是核心服务,我的意思是,这些服务本质上是被认为是基础的一部分。

我考虑过使用构造函数注入,但是出现了一些麻烦。

  • 我将不得不修改几乎每个构造函数来注入它的依赖,这意味着它将接受甚至是最基本的"服务"。我害怕在每个构造函数中有3或4个参数。
  • 但另一件更糟糕的事情是,如果框架的最终用户找不到无参数构造函数,他们会不高兴的。你认为创建一个TextBox需要传递它真正需要的所有依赖关系吗?

最终用户可能会被new TextBox(dep1, dep2, dep3)淹没。

那么,有没有一种好方法可以完全删除服务定位器而不使其过于复杂呢?

编辑

在我们正在讨论的团队中,他们仍然希望保持服务定位器争论以下内容。这是对话的一个片段:

我们不关心TextBox实现中的单一责任。我们我不是在写应用程序。它是一个UI框架!它应该。对最终用户隐藏内容和细节,而不是暴露它们。一个流畅的构建器的文本框?严重吗?我们不是在写一些web请求处理管道。

我同意他(Stack Overflow用户)写的所有内容,但是关于 应用程序。你想要的东西尽可能的可扩展和可重用。我们大概有10项服务。无处不在。如果我们在复杂的对象图上有50个依赖关系,我们做不到任何没有DI的东西。关于DI的问题是你有报酬为它。所以对我们来说,服务定位器是免费的,DI不是,它只是不可维护——通过更复杂的API,通过向后的API/ABI兼容性,等等。我们在不知道的情况下为服务定位器付费什么类需要作为它的依赖,以及提供时的一些问题不同实例的不同依赖实现。但我们我也不需要,所以服务定位器是免费的。迪不是。

你同意吗?为什么?

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

我将不得不修改几乎每个构造函数来注入它的依赖,这意味着它将接收甚至是最基本的"服务"。我害怕在每个构造函数中有4或5个参数。

如果你在一个构造函数中有4或5个服务,这表明你违反了单一职责原则。此时,是时候重构为聚合服务(或者称为facade服务)了。

但是另一件更糟糕的事情是框架的最终用户将没有无参数构造函数。你认为创建一个文本框需要你传递它真正需要的所有依赖关系吗?

首先要弄清楚你是在设计一个框架还是一个

但无论哪种方式,你都应该使它能够注入服务来覆盖,但提供逻辑默认行为。实现这一目标的最佳方法是使用流畅构建器来组合您的服务。也就是说,公共API将由构建器组成,这些构建器将完成底层服务的所有配置。然后,您可以公开允许根据最终用户的需要注入自定义服务的重载(例如,一个重载使用构建器构建默认的服务集,另一个重载只接受抽象)。

下面是一个快速示例,展示了如何做到这一点:

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);
    }
)
使用

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();

引用:

  • http://blog.ploeh.dk/2014/05/19/di-friendly-framework/
  • http://blog.ploeh.dk/2014/05/19/di-friendly-library/