依赖注入 - 正确使用接口?

时间:2012-12-17 17:35:10

标签: java dependency-injection inversion-of-control

我一直在阅读DI和最佳做法,但仍然没有找到这个问题的答案。我什么时候应该使用接口?

  1. 一些开发人员建议为每个正在注入的对象添加接口。这将成为模块化应用程序。
  2. 其他一些人反对这一点。
  3. 所以我的问题是哪一个是正确的?

    修改

    以下是双方,我仍然没有看到使用接口的优势。在这两种情况下,我都可以轻松地模拟类,并更改实现

    使用接口

    bind(IUserStorage.class).to(UserStorage.class);
    // Unit test
    bind(IUserStorage.class).to(Mock(UserStorage.class));
    

    不使用接口

    bind(UserStorage.class).to(UserStorage.class);
    // Unit test
    bind(UserStorage.class).to(Mock(UserStorage.class));
    

4 个答案:

答案 0 :(得分:4)

我不敢相信使用接口是OOP原则!

我肯定会在这种情况下使用接口。这意味着您松散地耦合您的组件,并且可以轻松模拟和/或替代替代品。许多DI框架将使用接口来提供附加功能(例如,创建映射到真实对象的代理对象,但具有其他功能)。

因此我会尝试使用接口,除了最简单的注入对象之外的所有接口。在某个阶段,你会想要利用可替代性,框架代码生成等,并且改进界面使用是一个额外的痛苦,在项目开始时很容易避免。

答案 1 :(得分:3)

基于接口的设计是IoC的基石,这里是基于接口的设计的简短描述(对不起,我正在引用我自己的博客,但我刚刚完成article关于这一点,它是我的MS论文的摘录:

  

Nandigam等人。将基于接口的设计定义为“一种开发方式   面向对象的系统,其中一个有意识地和主动地定义   并在设计中尽可能使用接口以获得收益   用接口设计“[Nan09]   基于接口的设计遵循“程序到界面”的原则,   不是实施“。这个原则带来以下好处   得到的系统[Dav03]:灵活性(描述系统   变更的稳健性,可扩展性(系统可以轻松实现)   容纳添加物)和可插拔性(允许的能力)   在运行时替换具有相同接口的对象。)

将界面设计与 IoC 混合后,您将获得以下好处:

  1. 任务与实施解耦
  2. 增加模块性,模块仅依靠其合约(接口)依赖其他模块。
  3. 增加可插拔性,更换模块对其他模块没有级联效应。
  4. 要回答你的问题,我会使用不同类型模块的接口。例如,每个服务或存储库一个。

    我不为控制器或模型类(MVC应用程序)创建接口。

    所有这些,作为副作用,有助于测试

答案 2 :(得分:2)

如果您使用接口或至少是抽象/可继承的类,您可以通过在DI / IoC配置中轻松交换实现(注入另一个类)来更改程序的行为。 使用接口是一种很好的做法(imho)。如果您正在编写需要模拟的UnitTests,这一点尤为重要。如果您不使用接口,那么编写具有良好覆盖范围的UnitTests(在大多数“真实世界”情况下并不是不可能)要困难得多。

我认为如果注入的部分可能会发生变化,应该使用界面。扩展您的实施应该很容易,请参阅Open-Closed-Principle。 =>这将需要交换模块/部件/实现...问问自己,如果你的类没有要覆盖的虚函数,你将被迫改变实现会发生什么。

至少为公共类使用接口 /部分代码(其他程序员会使用的部分)。

查看您的样本。 问题在于布线部分而不仅仅是接口作为接口的(默认)实现(绑定有效,但布线可能会中断)。

例如,如果您有2个实现(此处为C#示例,在Java等中也应该相同):

public interface IUserStorage
{
  void Write(object something);
}

public class UserStorageTextFile : IUserStorage
{
   public void Write(object something) { ... }; // stores to text file
}

public class UserStorageDB : IUserStorage
{
   public void Write(object something) { ... }; // stores to DB
}

public class MyStorageClient
{
   public MyStorageClient(IUserStorage storage) { ... } // copy to private field and use it etc.
}

根据您的IoC,它应该很容易接线 MyStorageClient的一个实例,用于绑定IUserStorage。

bind(IUserStorage.class).to(UserStorageDB.class); // Java sample, eh?

但是如果您的MyStorageClient强烈要求使用DB ......

public class MyStorageClient
{
   public MyStorageClient(UserStorageDB storage) { ... } // copy to private field and use it etc.
}

...用UserStorageTextFile类连接它是不可能的,除了UserStorageTextFile继承自UserStorageDB ...但是为什么你要依赖于例如UserStorageDB ... Oracle驱动程序(UserStorageDB需要),如果您只想编写一个简单的文本文件?

我认为样本足够清晰,并显示使用接口的好处......

但如果没有......尝试这样做:

bind(UserStorageDB.class).to(UserStorageTextFile.class);

// and in another config/module/unitTest
bind(UserStorageTextFile.class).to(Mock(UserStorageDB.class));

// and try to wire it against your client class, too (both ways, meaning one config for TextFile and load a config for the DB after changing only the configuration)

答案 3 :(得分:-1)

你的问题陈述“一些开发者[为此]”和“一些开发者[反对这个]”,所以没有正确的答案。但这就是我同意interfaces are overused

的原因

如果要创建库,选择何时使用接口很重要。当您不控制代码的使用方式时,很难创建可维护的合同。

但是,如果要创建应用程序,则不太可能需要接口,因为类的公共接口可以作为使用代码的可维护合同。假设版本1看起来像这样:

public class UserStorage
{
    public void Store(User user) { /* ... */ }
}

您甚至不需要重构工具来将其更改为:

public interface UserStorage
{
    public void Store(User user);
}

class TheImplementation implements IUserStorage
{
    public void Store(User user) { /* ... */ }
}

然后,您可以轻松使用重构工具将界面重命名为IUserStorage

所以当你编写非库代码时,你通常可以逃避一个类,直到你需要可交换的实现,装饰器等。你应该在类的公共接口时使用一个接口不适合您的需求。 (例如,请参阅interface segregation principle

简而言之 - 在应用程序代码中,具有类的1:1接口是不必要的间接。