What do programmers mean when they say, “Code against an interface, not an object.”?

前端 未结 7 1722
情书的邮戳
情书的邮戳 2020-11-27 11:21

I\'ve started the very long and arduous quest to learn and apply TDD to my workflow. I\'m under the impression that TDD fits in very well with IoC principle

相关标签:
7条回答
  • 2020-11-27 11:41

    Consider:

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

    Because MyMethod accepts only a MyClass, if you want to replace MyClass with a mock object in order to unit test, you can't. Better is to use an interface:

    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();
        }
    }
    

    Now you can test MyMethod, because it uses only an interface, not a particular concrete implementation. Then you can implement that interface to create any kind of mock or fake that you want for test purposes. There are even libraries like Rhino Mocks' Rhino.Mocks.MockRepository.StrictMock<T>(), which take any interface and build you a mock object on the fly.

    0 讨论(0)
  • 2020-11-27 11:41

    It's all a matter of intimacy. If you code to an implementation (a realized object) you are in a pretty intimate relationship with that "other" code, as a consumer of it. It means you have to know how to construct it (ie, what dependencies it has, possibly as constructor params, possibly as setters), when to dispose of it, and you probably can't do much without it.

    An interface in front of the realized object lets you do a few things -

    1. For one you can/should leverage a factory to construct instances of the object. IOC containers do this very well for you, or you can make your own. With construction duties outside of your responsibility, your code can just assume it is getting what it needs. On the other side of the factory wall, you can either construct real instances, or mock instances of the class. In production you would use real of course, but for testing, you may want to create stubbed or dynamically mocked instances to test various system states without having to run the system.
    2. You don't have to know where the object is. This is useful in distributed systems where the object you want to talk to may or may not be local to your process or even system. If you ever programmed Java RMI or old skool EJB you know the routine of "talking to the interface" that was hiding a proxy that did the remote networking and marshalling duties that your client didn't have to care about. WCF has a similar philosophy of "talk to the interface" and let the system determine how to communicate with the target object/service.

    ** UPDATE ** There was a request for an example of an IOC Container (Factory). There are many out there for pretty much all platforms, but at their core they work like this:

    1. You initialize the container on your applications startup routine. Some frameworks do this via config files or code or both.

    2. You "Register" the implementations that you want the container to create for you as a factory for the interfaces they implement (eg: register MyServiceImpl for the Service interface). During this registration process there is typically some behavioral policy you can provide such as if a new instance is created each time or a single(ton) instance is used

    3. When the container creates objects for you, it injects any dependencies into those objects as part of the creation process (ie, if your object depends on another interface, an implementation of that interface is in turn provided and so on).

    Pseudo-codishly it could look like this:

    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();
    
    0 讨论(0)
  • 2020-11-27 11:43

    It means think generic. Not specific.

    Suppose you have an application that notify the user sending him some message. If you work using an interface IMessage for example

    interface IMessage
    {
        public void Send();
    }
    

    you can customize, per user, the way they receive the message. For example somebody want to be notified wih an Email and so your IoC will create an EmailMessage concrete class. Some other wants SMS, and you create an instance of SMSMessage.

    In all these case the code for notifying the user will never be changed. Even if you add another concrete class.

    0 讨论(0)
  • 2020-11-27 11:43

    The big advantage of programming against interfaces when performing unit testing is that it allows you to isolate a piece of code from any dependencies you want to test separately or simulate during the testing.

    An example I've mentioned here before somewhere is the use of an interface to access configuration values. Rather than looking directly at ConfigurationManager you can provide one or more interfaces that let you access config values. Normally you would supply an implementation that reads from the config file but for testing you can use one that just returns test values or throws exceptions or whatever.

    Consider also your data access layer. Having your business logic tightly coupled to a particular data access implementation makes it hard to test without having a whole database handy with the data you need. If your data access is hidden behind interfaces you can supply just the data you need for the test.

    Using interfaces increases the "surface area" available for testing allowing for finer grained tests that really do test individual units of your code.

    0 讨论(0)
  • 2020-11-27 11:50

    When programming against an interface you will write code that uses an instance of an interface, not a concrete type. For instance you might use the following pattern, which incorporates constructor injection. Constructor injection and other parts of inversion of control aren't required to be able to program against interfaces, however since you're coming from the TDD and IoC perspective I've wired it up this way to give you some context you're hopefully familiar with.

    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();
            }
        }
    }
    

    The repository object is passed in and is an interface type. The benefit of passing in an interface is the ability to 'swap out' the concrete implementation without changing the usage.

    For instance one would assume that at runtime the IoC container will inject a repository that is wired to hit the database. During testing time, you can pass in a mock or stub repository to exercise your PeopleOverEighteen method.

    0 讨论(0)
  • Test your code like someone who would use it after reading the documentation. Do not test anything based on knowledge you have because you have written or read the code. You want to make sure that your code behaves as expected.

    In the best case you should be able to use your tests as examples, doctests in Python are a good example for this.

    If you follow these guidelines changing the implementation shouldn't be an issue.

    Also in my experience it is good practice to test each "layer" of your application. You will have atomic units, which in itself have no dependencies and you will have units which depend on other units until you eventually get to the application which in itself is a unit.

    You should test each layer, do not rely on the fact that by testing unit A you also test unit B which unit A depends on (the rule applies to inheritance as well.) This, too, should be treated as an implementation detail, even though you might feel as if you are repeating yourself.

    Keep in mind that once written tests are unlikely to change while the code they test will change almost definitely.

    In practice there is also the problem of IO and the outside world, so you want to use interfaces so that you can create mocks if necessary.

    In more dynamic languages this is not that much of an issue, here you can use duck typing, multiple inheritance and mixins to compose test cases. If you start disliking inheritance in general you are probably doing it right.

    0 讨论(0)
提交回复
热议问题