当程序员说:“对接口而不是对象进行编码”时,程序员意味着什么?

我已经开始了漫长而艰苦的学习,并将TDD应用于我的工作stream程。 我觉得TDD非常符合IoC原则。

在这里浏览一些TDD标记的问题之后,我发现用接口进行编程是一个好主意,而不是对象。

你能提供简单的代码例子吗?以及如何将它应用于实际的使用情况? 简单的例子对于我(和其他想学习的人)掌握这些概念至关重要。

非常感谢。

考虑:

 class MyClass { //Implementation public void Foo() {} } class SomethingYouWantToTest { public bool MyMethod(MyClass c) { //Code you want to test c.Foo(); } } 

因为MyMethod只接受一个MyClass ,所以如果你想用一个模拟对象replaceMyClass来进行unit testing,你不能。 更好的是使用一个接口:

 interface IMyClass { void Foo(); } class MyClass : IMyClass { //Implementation public void Foo() {} } class SomethingYouWantToTest { public bool MyMethod(IMyClass c) { //Code you want to test c.Foo(); } } 

现在你可以testingMyMethod ,因为它只使用一个接口,而不是特定的具体实现。 然后,您可以实现该接口来创build任何您想要的testing用途的模拟或伪造。 甚至还有像Rhino Rhino.Mocks.MockRepository.StrictMock<T>() ,它们可以接受任何接口,并为您build立一个模拟对象。

这都是亲密的问题。 如果你对一个实现(一个已经实现的对象)进行编码,那么你就和这个“其他”代码有着密切的关系。 这意味着你必须知道如何构build它(即它有什么依赖关系,可能作为构造函数,可能作为setter),什么时候处理它,如果没有它,你可能不会做太多的事情。

实现对象前面的界面让你可以做一些事情 –

  1. 对于你可以/应该利用工厂来构build对象的实例。 国际奥委会容器为你做得非常好,或者你可以自己做。 由于build设责任不在您的责任范围之内,您的代码可以假设它正在得到它所需要的。 在工厂墙的另一边,你可以构build真实的实例,或者模拟类的实例。 在生产中你当然会使用真实的,但是为了testing,你可能想要创build存根或dynamic模拟实例来testing各种系统状态,而不必运行系统。
  2. 你不必知道对象在哪里。 这在分布式系统中是非常有用的,在这个系统中你要和之交谈的对象可能或者可能不在你的进程或系统本地。 如果您曾经编写过Java RMI或者旧的skool EJB,那么您就知道了“与接口交谈”的例程,这个例程隐藏了一个代理,这个代理完成了客户不需要关心的远程联网和编组任务。 WCF有一个类似于“与接口交谈”的哲学,让系统确定如何与目标对象/服务进行通信。

**更新**有一个IOC容器(工厂)的例子的请求。 几乎所有的平台都有很多,但是他们的核心是这样的:

  1. 您在应用程序启动例程上初始化容器。 一些框架通过configuration文件或代码或两者来完成。

  2. 您将您希望容器为您创build的实现注册为它们实现的接口的工厂(例如:为服务接口注册MyServiceImpl)。 在这个注册过程中,通常会有一些您可以提供的行为策略,例如每次创build新实例或使用单个(吨)实例

  3. 当容器为你创build对象时,它将任何依赖关系注入到这些对象中,作为创build过程的一部分(即,如果你的对象依赖于另一个接口,则依次提供该接口的实现等等)。

伪代码可能看起来像这样:

 IocContainer container = new IocContainer(); //Register my impl for the Service Interface, with a Singleton policy container.RegisterType(Service, ServiceImpl, LifecyclePolicy.SINGLETON); //Use the container as a factory Service myService = container.Resolve<Service>(); //Blissfully unaware of the implementation, call the service method. myService.DoGoodWork(); 

在针对接口进行编程时,您将编写使用接口实例的代码,而不是具体的types。 例如,你可以使用下面的模式,其中包含构造函数注入。 构造器注入和控制反转的其他部分不需要能够针对接口进行编程,但是由于您是从TDD和IoCangular度来看的,所以我通过这种方式将它连接起来,以便给出一些您希望的上下文熟悉。

 public class PersonService { private readonly IPersonRepository repository; public PersonService(IPersonRepository repository) { this.repository = repository; } public IList<Person> PeopleOverEighteen { get { return (from e in repository.Entities where e.Age > 18 select e).ToList(); } } } 

存储库对象被传入并且是一个接口types。 传递接口的好处是可以在不改变用法的情况下“交换”具体实现。

举个例子,假设在运行时,IoC容器将注入一个连接到数据库的存储库。 在testing期间,您可以传递一个模拟库或存根库来练习您的PeopleOverEighteen方法。

这意味着认为通用。 不具体。

假设你有一个应用程序通知用户给他发送一些消息。 例如,如果您使用接口IMessage工作

 interface IMessage { public void Send(); } 

您可以自定义每个用户收到邮件的方式。 例如,有人希望通过电子邮件得到通知,因此您的IoC将创build一个EmailMessage具体类。 其他一些需要SMS,并创build一个SMSMessage的实例。

在所有这些情况下,通知用户的代码将永远不会改变。 即使你添加了另一个具体的类。

在执行unit testing时对接口进行编程的一大优点是,它允许您在testing过程中单独testing或模拟任何依赖项中的代码。

我之前在某处提到过的一个例子是使用一个接口来访问configuration值。 而不是直接看ConfigurationManager,你可以提供一个或多个接口,让你访问configuration值。 通常你会提供一个从configuration文件中读取的实现,但是对于testing,你可以使用一个只返回testing值或者抛出exception或其他东西的实现。

考虑你的数据访问层。 使您的业务逻辑与特定的数据访问实现紧密结合,使得在没有整个数据库方便地处理所需数据的情况下进行testing变得非常困难。 如果您的数据访问被隐藏在接口后面,您可以提供testing所需的数据。

使用接口增加了可用于testing的“表面积”,从而允许进行更细粒度的testing,这些testing真的可以testing代码的各个单元。

在阅读文档之后,testing你的代码,就像会使用它的人一样。 由于您已经编写或阅读了代码,因此不要根据您所拥有的知识来testing任何内容。 你想确保你的代码按预期行事

在最好的情况下,你应该能够使用你的testing作为例子,Python中的文档testing就是一个很好的例子。

如果你遵循这些准则,改变实现不应该是一个问题。

另外在我的经验中,testing应用程序的每个“层”是一个很好的做法。 你将拥有primefaces单位,它本身没有任何依赖性,你将拥有依赖于其他单位的单位,直到你最终到达本身是一个单位的应用程序。

你应该testing每一层,不要依靠testing单元A你也testing单元A依赖的单元B(这个规则也适用于inheritance)的事实。这也应该被视为一个实现细节,甚至尽pipe你可能会觉得好像在重复着自己。

请记住,一旦书面testing不太可能改变,而他们testing的代码几乎肯定会改变。

在实践中也存在IO和外部世界的问题,所以你想要使用接口,以便可以在必要时创build模拟。

在更dynamic的语言中,这不是什么大问题,在这里你可以使用鸭子打字,多重inheritance和mixin来组合testing用例。 如果你开始不喜欢inheritance,你可能做得很对。

这个截屏video解释了C#实践中的敏捷开发和TDD。

通过对接口进行编码意味着在testing中,可以使用模拟对象而不是实际对象。 通过使用一个好的模拟框架,你可以在你的模拟对象中做任何你喜欢的事情。