我一直在阅读DI和最佳做法,但仍然没有找到这个问题的答案。我什么时候应该使用接口?
所以我的问题是哪一个是正确的?
修改
以下是双方,我仍然没有看到使用接口的优势。在这两种情况下,我都可以轻松地模拟类,并更改实现
使用接口
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 :(得分:4)
我不敢相信使用接口是OOP原则!
我肯定会在这种情况下使用接口。这意味着您松散地耦合您的组件,并且可以轻松模拟和/或替代替代品。许多DI框架将使用接口来提供附加功能(例如,创建映射到真实对象的代理对象,但具有其他功能)。
因此我会尝试使用接口,除了最简单的注入对象之外的所有接口。在某个阶段,你会想要利用可替代性,框架代码生成等,并且改进界面使用是一个额外的痛苦,在项目开始时很容易避免。
答案 1 :(得分:3)
基于接口的设计是IoC的基石,这里是基于接口的设计的简短描述(对不起,我正在引用我自己的博客,但我刚刚完成article关于这一点,它是我的MS论文的摘录:
Nandigam等人。将基于接口的设计定义为“一种开发方式 面向对象的系统,其中一个有意识地和主动地定义 并在设计中尽可能使用接口以获得收益 用接口设计“[Nan09] 基于接口的设计遵循“程序到界面”的原则, 不是实施“。这个原则带来以下好处 得到的系统[Dav03]:灵活性(描述系统 变更的稳健性,可扩展性(系统可以轻松实现) 容纳添加物)和可插拔性(允许的能力) 在运行时替换具有相同接口的对象。)
将界面设计与 IoC 混合后,您将获得以下好处:
要回答你的问题,我会使用不同类型模块的接口。例如,每个服务或存储库一个。
我不为控制器或模型类(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接口是不必要的间接。