服务定位器是框架的好模式吗?
本文关键字:模式 定位器 框架 服务 | 更新日期: 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/