2012-12-17 84 views
2

我一直在阅读关于DI和最佳实践,但仍未找到这个问题的答案。我应该何时使用接口?依赖注入 - 正确使用接口?

  1. 有些开发人员建议为每个要注入的对象添加接口。这将构成一个模块化应用程序。
  2. 其他一些反对这一点。

所以我的问题是哪一个是正确的?

编辑:

以下是双方的,我还没有看到使用接口的优势。在这两种情况下,我可以很容易地模拟类,并改变实现

使用接口

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)); 
+0

请不要为每个容器管理的依赖项创建一个接口,除非有一些令人信服的理由。如果需要,稍后可以很容易地将类重构为接口。 –

+0

@aryaxt:为什么要创建/使用一个接口破解OOP原则。你能举一个例子/报价吗? – Beachwalker

+1

@ default.kramer:只有拥有/维护代码并且可以更改代码才是简单的。与具有虚拟功能(甚至密封)的类相比,使用接口更符合开放 - 关闭原则。 – Beachwalker

回答

4

我不能相信使用接口时againt OOP原则!

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

因此我会尝试使用接口,为所有,但最琐碎的注入对象。在某个阶段,您将要利用替代性,框架代码生成等,并且改进界面的使用是一个额外的麻烦,在项目开始时很容易避免。

+0

你会尽可能为所有注入的对象使用接口吗? – aryaxt

+0

我肯定会尝试所有的,但最微不足道的 –

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. 
} 

...这是IMPOSIBLE与除UserStorageTextFile的UserStorageTextFile类是从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

界面设计是国际奥委会的基石,这里是基于接口的设计的简要说明(遗憾的是,我引用我自己的博客,但我刚刚完成了一个article这一点,这是从我的硕士学位论文的摘录):

Nandigam et al。将基于接口的设计定义为“一种开发面向对象系统的方式,在这种系统中,人们有意识地并主动地定义了 ,并在设计中尽可能使用接口来获得使用接口进行设计的好处”[Nan09]。采用基于接口的设计的应用遵循“编程接口, 不是实现”的原则。该原理具有以下优点 到所得到的系统[Dav03]:灵活性(描述了系统 鲁棒性改变),扩展(与系统可 容纳增加的容易性)和可插拔(能力允许 在运行时用相同的接口替换对象)。

一旦你与的IoC混合接口设计可以得到以下好处:

  1. 任务是分离从实现。
  2. 增加模块性其中模块仅依赖于其合约(接口)上的其他模块。
  3. 增加可插拔性并且替换模块不会对其他模块产生级联效应。

要回答你的问题,我会使用不同类型的模块的接口。例如,每个服务或存储库一个。

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

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

+0

这正是我目前正在做的,ViewController和模型没有被注入,所以我不会将接口应用到他们。 – aryaxt

-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的接口在应用程序代码中是不必要的间接方向。

+0

但是,如果您的UserStorage默认实现需要一堆依赖性来工作,那么这很糟糕......这也会将这些依赖关系添加到您的指派类(即使没有合乎逻辑的原因)。此外,您的示例不显示任何虚拟方法,因此改变行为并使用关键字“新”(公共新的无效存储(用户用户)...)将很难找到错误。 – Beachwalker

+0

那么这是java,所以虚拟是默认的...但重点是,在重构前版本中只有一个implementation_。它仍然很容易嘲笑。 –