【问题标题】:Regarding to Type in C#关于 C# 中的类型
【发布时间】:2018-11-19 17:31:36
【问题描述】:

我尝试使用 C# DI 方法来实现一些东西。以下是我的代码 sn-p。

public interface IMessageService
{
    void Send(string uid, string password);
}

public class MessageService : IMessageService
{
    public void Send(string uid, string password)
    {
    }
}

public class EmailService : IMessageService
{
    public void Send(string uid, string password)
    { 
    }
}

以及创建ServiceLocator的代码:

public static class ServiceLocator
{

    public static object GetService(Type requestedType)
    {
        if (requestedType is IMessageService)
        {
            return new EmailService();
        }
        else
        {
            return null;
        }
    }
}

现在,我用

创建一个测试代码
public class AuthenticationService
{
    private IMessageService msgService;
    public AuthenticationService()
    {
        this.msgService = ServiceLocator
          .GetService(typeof(IMessageService)) as IMessageService;
    }
}

但是,看起来,我总是得到GetService() 函数返回的null。相反,我希望通过GetService() 函数获得EmailService 对象,那么如何正确执行呢?

【问题讨论】:

  • 对于那些出于任何原因尝试使用这种模式的人,即使是对于复杂系统,请知道服务定位器是anti-pattern,如果您认为需要它,请重新考虑你的设计摆脱它。
  • @CodeNotFound - 当它像这段代码一样返回一个硬编码的类时,你是说它是一种反模式吗?还是你说的更笼统?如果有,为什么?
  • @Enigmativity 我一般来说是这样说的 :) 为什么?由于我在第一条评论中添加的 URL 中的博客文章中解释的所有原因。硬编码分辨率也可能被标记为不好的做法。
  • @CodeNotFound - 我认为这有点过于简单了。一个实现只需要契约(接口)和行为(单元测试)——考虑到这两件事,这个模式运作良好。添加装饰器和动态加载,您可以创建一个经过良好测试的灵活开发环境。我认为如果你只走一半,这是一种反模式。
  • @Enigmativity return new EmailService(); 丢失了行为(单元测试)。在对使用IMessageService 的类进行单元测试时,你如何模拟它。也许我错过了什么

标签: c#


【解决方案1】:

您传入的是Type 的一个实例。

所以requestedType is IMessageService 这个条件永远不会是true

你需要做的是

public static object GetService(Type requestedType)
{
    if (requestedType == typeof(IMessageService))
    {
        return new EmailService();
    }
    else
    {
        return null;
    }
}

顺便说一句,这是一个非常糟糕的模式——你所谓的service locator 对具体类型有具体的了解。你最好使用反射或一些传统的 IoC 注册模式来使其通用。

【讨论】:

  • @CodeNotFound 请不要通过编辑我的答案把话放在我的嘴里。来自 OP 的服务定位器的具体实现很糟糕。我不认为我们应该永远使用它,或者它在复杂系统中没有位置。
  • 你知道,这让我开始思考……这真的不好吗?如果您无论如何要推出自己的 DI/ServiceLocator,并且它并不意味着可以在项目之间重用,那么将其分成两部分可以获得什么?通用定位器/实例化器和注册表。它的代码更多、更复杂、更分散、更间接。 Go-to-definition 不足以找到某个对象的构造位置/方式。如果涉及反射,甚至可能会慢一点。你得到了……好吧,我什么也看不见。它不太易读,并且有更多的错误机会。
  • @Vilx- 如果您正在测量代码覆盖率,并以这种方式编写代码,则必须为您注册的每个类编写一个测试。如果您将逻辑拆分为通用和注册,则可以测试一次解析并编写另一个测试以确保注册所有必要的类。或者,您甚至可以编写一个基于反射的注册过程,根据它们的接口添加所有类型。
  • 关于测试 - write a test for each class that you register[a] test that ensures all necessary classes are registered - 这里到底有什么区别? :) 我特别不喜欢反射过程,因为它太神奇了。除非你是写这篇文章的人,否则其他人都会对魔法到底是如何运作的感到摸不着头脑。就是这样 - 我真的不喜欢 magic 代码 - 以一种神秘的方式发生的事情,没有可见的代码行。 “它只是工作”是 UX 设计的一个很好的原则,但对于代码设计来说却是一个糟糕的原则。
【解决方案2】:

我尝试使用 C# DI 方法来实现一些东西。以下是我的代码 sn-p

没有这样的模式称为“C# DI 方法”。我认为我们这里的任务是为 DI 使用 ServiceLocator 模式。不要那样做!

ServiceLocator 可以说是一个anti-pattern 并导致维护噩梦,因为隐藏了类依赖关系。在大多数现实场景中,我们应该避免使用它。

借助一些 DI 框架,例如 SimpleInjector(它可以是任何其他知名的 DI 框架),您可以获得相同的结果。然而,这一次代码将更易于维护并且更容易测试。 为此,我们可以创建一个Mock<IMessageService> 并将其对象传递给EmailService 的构造函数。

但是让我们回到主题,看看how we could use Simpleinjector here

public class AuthenticationService
{
    private readonly IMessageService _msgService;

    public AuthenticationService(IMessageService msgService)
    {
        this._msgService = msgService;
    }
}

为了在代码中的某处使用它,我们需要register this dependency。一个最小的代码示例是:

var container = new SimpleInjector.Container();
container.Register<IMessageService, EmailService>();
container.Verify();

这就是它所需要的!

附: 这不是这个特定 DI 框架的广告。随意使用任何其他框架,我在这个例子中使用过它,因为我更熟悉它

【讨论】:

  • 这不是 OP 问题的答案。我投了赞成票,因为它解释了如何转向一个好的做法:-)
  • @CodeNotFound 是的,我只是想指出这样一个事实,即服务定位器模式在这里是一个伪劣的解决方案。有时一个好的答案是间接的......
  • 英语似乎是 OP 的第二语言。如果您想提供帮助,可以编辑问题。
猜你喜欢
  • 2011-12-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-10
  • 1970-01-01
  • 1970-01-01
  • 2020-07-05
  • 1970-01-01
相关资源
最近更新 更多