0

I'm working on a simple DI library, and on the subject of using services through interfaces, I can't find the usefulness. Perhaps it's because I dont fully or properly understand how an interface should be used during the process of injecting dependencies.

Here's what I (think I) understand:

DI consists primarily of clients and services. Interfaces allow the injector to tell the client which variables and methods are available through a specific service.

If that isn't wrong:

It seems to me that the interface could pass available property names and what type they are, so my client can know if they're functions or values, but that doesn't seem useful since I can do that with or without the interface. Whether the methods/values are utilized via an interface or via the injected service, I can know their types because I can use the typeof operand (JavaScript) regardless to check the type of the property I'm about to use.

So I think that I must not have a proper understanding of what interfaces should do. How are interfaces used in dependency injection?

  • you mention that you could pass available property names and what type they are without the interface, how? – gnat Oct 2 '15 at 14:26
  • Whether the methods/values are utilized via an interface or via the injected service, I can know their types because I can use the typeof operand (JavaScript) regardless to check the type of the property I'm about to use. @gnat – Viziionary Oct 2 '15 at 14:43
  • answers given so far appear to ignore that you work with Javascript, are you okay with that? – gnat Oct 2 '15 at 15:20
  • @gnat I didnt specify JS because I think good answers will cite programming principles, not language specific functionality, and a JavaScript developer worth his salt should be able to implement basically any well explained programming principle/pattern in JS. – Viziionary Oct 2 '15 at 15:30
  • 1
    Javascript does not support interfaces. – Phil Sandler Oct 2 '15 at 15:35
5

The benefit isn't that the interface declares the members and methods that a client needs--which is exactly what a concrete class can do. The benefit is that a concrete class can implement an interface, which allows you to swap out the dependency at run-time. For testing, you may want to pass in a mock implementing the same interface, but without all the overhead of the usual class (database calls, perhaps). Your class only depends on an interface, but the implementation is left for the DI/IoC container to inject.

  • Is a concrete class the same thing as an "abstract class"? – Viziionary Oct 2 '15 at 14:44
  • 2
    It's actually the opposite of an abstract class--it's the "normal" class. – mgw854 Oct 2 '15 at 14:59
4

There are at least three reasons you might want to inject an interface instead of a concrete class:

  1. You have multiple implementations of that interface, and want the container to select one at run-time.
  2. You want to write unit tests that isolate out the injected service, and so want to provide a mock implementation for the test (this is arguably the same as #1, since a mock is just another implementation, even if it's provided by a mock framework).
  3. You have some form of Onion Architecture, where your consuming class is in an assembly does not have knowledge of the implementation's assembly, but does have knowledge of a common assembly (where the interface would reside).

It is otherwise perfectly acceptable to inject concrete types. I actually asked about injecting concrete types on SO here.

  • 1
    I selected the answer that seemed to explain everything in the most succint way, but this answer along with all of the others combined really helped me to understand this. Thanks for the great answer! – Viziionary Oct 3 '15 at 6:18
1

Here's an example from an angularjs project that is actually being used by large clients you probably do business with.

We have questions that are read in from json (technically, many of them start as xml and undergo a conversion process). The various question types all have different things that need to be added to them based on what our clients want to know. We could put all that "stuff" in the XML/json, but that's a lot of overhead and very error-prone. So instead, we create a "factory" Class for each question type that knows how to take in some initialization data and a question worth of JSON and add all that stuff to it. Then we have another Class whose responsibility is to get the JSON file, look to see what type of question it is, call $injector.$get, to retrieve the factory, initialize the factory and then call the createQuestion() method on each factory. This allows us to provide infinite flexibility on the types of questions we can parse--to add another, you just register another factory with Angular that knows what to do and nothing else needs to care.

Note that I don't actually provide or implement an Interface. I'm not working in TypeScript, so AFAIK there is no actual way to do that in just plain JavaScript. The presumption is if the injector finds something called questionTypeFactory, it has an init and createQuestion method.

We use the same pattern to synchronize things to audio. Our Json files that define that have a collection of "demoSteps" and each demo step has an "action" property. When the audio framework calls the callback, the callback rifles around in the $injector for actionNameCommand, passes in some initialization data, and calls its execute() method. Again, there's no actual Interface defined, it's simply presumed that if you find something called actionNameCommand nobody put it there without writing it correctly. And we have resharper templates that make it trivial to do so.

1

Dependency Injection is handing a piece of software its dependencies at runtime, instead of coding that piece of software in such a way that it has statically bound, hard-coded, intimate knowledge of the precise type and instance of each one of its dependencies.

For example, this is without DI:

function prepareToDrink()
{
     TheOneAndOnlyGloballyAvailableHeinekenBottle.open();
}

And this is with DI:

function prepareToDrink( Bottle anyBottle )
{
     anyBottle.open(); //also works with that Budweisser bottle. Also with champagne.
}

As you can see, not only will it work with any beer Bottle in the house, but also with other kinds of bottles, assuming that the other bottles are derived from the same basic Bottle class.

The definition you found chooses to call the "piece of software" a "client", and the dependency a "service", but elaborate talk of this kind can be misleading at times.

A dependency is really anything that your piece of software needs in order to do its job. (It depends on it.) It may be an object, or it may be an interface; but if it was to be an object, then that object would have to be of a specific class, or a class more derived from it, and this would be a bit limiting. We prefer to use interfaces so that we can pass any class we like, without it having to be derived from a specific class.

So, for example, if we want to also be able to open beer cans, but Can is not derived from Bottle, we can have both Can and Bottle implement the Openable interface, in which case we can do this:

function prepareToDrink( Openable anythingThatSupportsOpening )
{
    anythingThatSupportsOpening.open(); //opens bottles; also opens cans.
}
  • Ok I think I'm starting to understand - So it's actually the interface being injected? Rather than injecting a service, I can inject an interface which can swap out it's associated service, perhaps for unit testing? – Viziionary Oct 3 '15 at 6:02
  • This, along with other answers combined helped me understand, so +1 and thanks! I selected another answer based on succintness and clarity, but this answer helped me reach the understanding I was after! – Viziionary Oct 3 '15 at 6:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.