且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

接口或类的依赖注入

更新时间:2023-01-11 09:57:11

快速"答案

我会坚持使用接口.它们设计成为外部实体的消费合同.

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.

实施与消费

实现接口和使用接口是有区别的.添加方法会破坏实现,但不会破坏使用者.

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

删除方法显然会破坏消费者,但不会破坏实现 - 但是,如果您对消费者具有向后兼容性意识,则不会这样做.

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.

我的经历

我们经常与接口建立一对一的关系.这在很大程度上是一种形式,但您偶尔会得到很好的实例,其中接口很有用,因为我们存根/模拟测试实现,或者我们实际上提供了特定于客户端的实现.如果我们碰巧更改接口,这经常会破坏一个实现的事实并不是代码异味,在我看来,这只是您如何针对接口工作.

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. 针对任何新内容完全独立的界面:MyInterfaceV1MyInterfaceV2.
  2. 接口继承: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();
}