依赖注入接口或类 [英] Dependency injection with interfaces or classes

查看:192
本文介绍了依赖注入接口或类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直内疚使用依赖注入的时候有我的接口和具体类之间的1对1的关系。当我需要一个方法添加到一个接口,我最终突破实现该接口的所有类。

I've been guilty of having a 1-to-1 relationship between my interfaces and concrete classes when using dependency injection. When I need to add a method to an interface, I end up breaking all the classes that implement the interface.

这是一个简单的例子,但是让我们假设我需要注入的 ILogger 进入我的课之一。

This is a simple example, but let's assume that I need to inject an ILogger into one of my classes.

public interface ILogger
{
    void Info(string message);
}

public class Logger : ILogger
{
    public void Info(string message) { }
}

有了这样的1对1的关系,感觉就像一个代码味道。因为我只有一个单一的实现,是否有任何潜在的问题,如果我创建了一个类,并标记信息方法,虚拟在我的测试覆盖而不必创建一个接口只为一个单一的类?

Having a 1-to-1 relationship like this feels like a code smell. Since I only have a single implementation, are there any potentially issues if I create a class and mark the Info method as virtual to override in my tests instead of having to create an interface just for a single class?

public class Logger
{
    public virtual void Info(string message)
    {
        // Log to file
    }
}

如果我需要另一种实现方式,我可以覆盖信息方法:

If I needed another implementation, I can override the Info method:

public class SqlLogger : Logger
{
    public override void Info(string message)
    {
        // Log to SQL
    }
}

如果每个类都将创造一个漏水的抽象特定的属性或方法,我可以提取出基类:

If each of these classes have specific properties or methods that would create a leaky abstraction, I could extract out a base class:

public class Logger
{
    public virtual void Info(string message)
    {
        throw new NotImplementedException();
    }
}

public class SqlLogger : Logger
{
    public override void Info(string message) { }
}

public class FileLogger : Logger
{
    public override void Info(string message) { }
}

为什么我没有标注的基类为抽象的原因是因为如果我想添加另一种方法,我不会破坏现有的实现。例如,如果我的 FileLogger 需要调试的方法,我可以更新基类日志不打破现有的 SqlLogger

The reason why I didn't mark the base class as abstract is because if I ever wanted to add another method, I wouldn't break existing implementations. For example, if my FileLogger needed a Debug method, I can update the base class Logger without breaking the existing SqlLogger.

public class Logger
{
    public virtual void Info(string message)
    {
        throw new NotImplementedException();
    }

    public virtual void Debug(string message)
    {
        throw new NotImplementedException();
    }
}

public class SqlLogger : Logger
{
    public override void Info(string message) { }
}

public class FileLogger : Logger
{
    public override void Info(string message) { }
    public override void Debug(string message) { }
}

同样,这是一个简单的例子,但是当我我宁愿一个接口?

Again, this is a simple example, but when I should I prefer an interface?

推荐答案

快速答

我要坚持接口。他们的设计的是消费对外部实体的合同。

I would stick with interfaces. They are designed to be contracts for consumption for external entities.

@JakubKonecki提到的多重继承。我觉得这是最大的理由,坚持使用接口,如果你强迫他们采取一个基类将成为在消费者一方非常明显......没有人喜欢基类后,他们被撵。

@JakubKonecki mentioned multiple inheritance. I think this is the biggest reason to stick with interfaces as it will become very apparent on the consumer side if you force them to take a base class... no one likes base classes being thrust upon them.

的更新快速答

您已经指出你的控制之外的接口实现的问题。一个好的方法是简单地创建一个新的接口,从旧的继承和修正自己的实现。然后,您可以通知其他球队一个新的界面。随着时间的推移,你可以弃用旧接口。

You have stated issues with interface implementations outside your control. A good approach is to simply create a new interface inheriting from the old one and fix your own implementation. You can then notify the other teams that a new interface is available. Over time, you can deprecate older interfaces.

不要忘了你可以使用的显式接口实现以帮助维持在逻辑相同的接口之间的一个很好的鸿沟,但不同的版本。

Don't forget you can use the support of explicit interface implementations to help maintain a nice divide between interfaces that are logically the same, but of different versions.

如果您希望这一切以配合DI,那么尽量不要定义新的接口,转而青睐补充。另外,限制客户端代码的变化,试图继承旧的新接口。

If you want all this to fit in with DI, then try not to define new interfaces and instead favour additions. Alternatively to limit client code changes, try to inherit new interfaces from old ones.

执行与功耗

有是<青霉>实施的接口和耗时的其之间的差。添加一个方法打破了实现(S),但不会打破消费者

There is a difference between implementing the interface and consuming it. Adding a method breaks the implementation(s), but does not break the consumer.

删除方法显然打破了消费,但不破的实现 - 但是你不会告发'T这样做,如果你是向后兼容的意识为您的消费者。

Removing a method obviously breaks the consumer, but does not break the implementation - however you wouldn't do this if you are backwards-compatibility conscious for your consumers.

我的经验

我们经常有一个接口,1对1的关系。这在很大程度上是一种形式,但你偶尔会很好的实例,因为我们在那里存根接口是有用/模拟测试实现,或者我们实际上提供客户特定的实现。这经常打破,如果我们发生改变接口一个实现不是一个代码味道,在我看来,事实上,它只是你对接口的工作方式。

We frequently have a 1-to-1 relationship with interfaces. It is largely a formality but you occasionally get nice instances where interfaces are useful because we stub / mock test implementations, or we actually provide client-specific implementations. The fact that this frequently breaks that one implementation if we happen to change the interface isn't a code smell, in my opinion, it is simply how you work against interfaces.

我们基于接口的方法现在站在我们非常有利,因为我们使用的技术,如工厂模式和DI元素,以改善老化的遗留代码库。测试能够迅速采取的,在代码库中存在查找明确使用(即不只是1-1具体类映射)。

Our interface-based approach is now standing us in good stead as we utilise techniques such as the factory pattern and elements of DI to improve an aged legacy code base. Testing was able to quickly take advantage of the fact that interfaces existed in the code base for many years before finding a "definitive" use (ie, not just 1-1 mappings with concrete classes).

基类缺点

基类是共享实施细则,共同的实体,事实上,他们能够做一些事情与公开分享的API类似的是一个副产品在我看来。接口被设计成公开分享API,所以使用它们。

Base classes are for sharing implementation details to common entities, the fact they are able to do something similar with sharing an API publicly is a by-product in my opinion. Interfaces are designed to share API publicly, so use them.

通过基类,你也可能得到实现细节泄露,例如,如果你需要做一些公众实施的另一部分使用。这些都是不利于保持一个清洁的公共API。

With base classes you could also potentially get leakage of implementation details, for example if you need to make something public for another part of the implementation to use. These are no conducive to maintaining a clean public API.

打破/支持实现

如果你去了,你可能会遇到困难的接口路由甚至改变接口,由于打破合同。另外,你提到,你可以打破你实现控制之外。有解决这一问题的方法有两种:

If you go down the interface route you may run into difficulty changing even the interface due to breaking contracts. Also, as you mention, you could break implementations outside of your control. There are two ways to tackle this problem:


  1. 国家,你不会打破消费者,但你不会支持实施

  2. 状态,一旦接口被公布,它永远不会改变。

我亲眼目睹了后者,我看到它有两种形式出现:

I have witnessed the latter, I see it come in two guises:


  1. 对于任何新的东西完全不同的接口: MyInterfaceV1 MyInterfaceV2

  2. 接口继承: MyInterfaceV2:MyInterfaceV1

  1. Completely separate interfaces for any new stuff: MyInterfaceV1, MyInterfaceV2.
  2. Interface inheritance: MyInterfaceV2 : MyInterfaceV1.

我个人不会的选择的走这条路,我会选择不支持从实现重大更改。但有时我们没有这样的选择。

I personally wouldn't choose to go down this route, I would choose to not support implementations from breaking changes. But sometimes we don't have this choice.

一些代码

public interface IGetNames
{
    List<string> GetNames();
}

// One option is to redefine the entire interface and use 
// explicit interface implementations in your concrete classes.
public interface IGetMoreNames
{
    List<string> GetNames();
    List<string> GetMoreNames();
}

// Another option is to inherit.
public interface IGetMoreNames : IGetNames
{
    List<string> GetMoreNames();
}

// A final option is to only define new stuff.
public interface IGetMoreNames 
{
    List<string> GetMoreNames();
}

这篇关于依赖注入接口或类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆