1

I've been reading up on how to write testable code and stumbled upon the Dependency Injection design pattern.

This design pattern is really easy to understand and there is really nothing to it, the object asks for the values rather then creating them itself.

However, now that I'm thinking about how this could be used the application im currenty working on I realize that there are some complications to it. Imagine the following example:

public class A{

   public string getValue(){
      return "abc";
   }
}


public class B{
   private A a;

   public B(A a){
      this.a=a;
   }
   public void someMethod(){ 
      String str = a.getValue();
   }
}

Unit testing someMethod () would now be easy since i can create a mock of A and have getValue() return whatever I want.

The class B's dependency on A is injected through the constructor, but this means that A has to be instantiated outside the class B so this dependency have moved to another class instead. This would be repeated many layers down and on some point instantiation has to be done.

Now to the question, is it true that when using Dependency Injection, you keep passing the dependencys through all these layers? Wouldn't that make the code less readable and more time consuming to debug? And when you reach the "top" layer, how would you unit test that class?

John Snow
  • 5,214
  • 4
  • 37
  • 44
  • It is not dependency injection. You have to create interface, after make implement it and instead of private A a in your class b use private IA ia, then pass in constructor object A and assign it to your variable ia – Alex Ovechkin Apr 08 '13 at 12:57

3 Answers3

5

I hope I understand your question correctly.

Injecting Dependencies

No we don't pass the dependencies through all the layers. We only pass them to layers that directly talk to them. For example:

public class PaymentHandler {

    private customerRepository;

    public PaymentHandler(CustomerRepository customerRepository) {
        this.customerRepository = customerRepository;
    }

    public void handlePayment(CustomerId customerId, Money amount) {
        Customer customer = customerRepository.findById(customerId);
        customer.charge(amount);
    }
}

public interface CustomerRepository {
    public Customer findById(CustomerId customerId);
}

public class DefaultCustomerRepository implements CustomerRepository {

    private Database database;    

    public CustomerRepository(Database database) {
        this.database = database;
    }

    public Customer findById(CustomerId customerId) {
        Result result = database.executeQuery(...);
        // do some logic here
        return customer;
    }
}

public interface Database {
    public Result executeQuery(Query query);
}

PaymentHandler does not know about the Database, it only talks to CustomerRepository. The injection of Database stops at the repository layer.

Readability of the code

When doing manual injection without framework or libraries to help, we might end up with Factory classes that contain many boilerplate code like return new D(new C(new B(), new A()); which at some point can be less readable. To solve this problem we tend to use DI frameworks like Guice to avoid writing so many factories.

However, for classes that actually do work / business logic, they should be more readable and understandable as they only talk to their direct collaborators and do the work they need to do.

Unit Testing

I assume that by "Top" layer you mean the PaymentHandler class. In this example, we can create a stub CustomerRepository class and have it return a Customer object that we can check against, then pass the stub to the PaymentHandler to check whether the correct amount is charged.

The general idea is to pass in fake collaborators to control their output so that we can safely assert the behavior of the class under test (in this example the PaymentHandler class).

Why interfaces

As mentioned in the comments above, it is more preferable to depend on interfaces instead of concrete classes, they provide better testability(easy to mock/stub) and easier debugging.

Hope this helps.

Jimmy Au
  • 2,065
  • 1
  • 13
  • 14
3

Well yes, that would mean you have to pass the dependencies over all the layers. However, that's where Inversion of Control containers come in handy. They allow you to register all components (classes) in the system. Then you can ask the IoC container for an instance of class B (in your example), which would automatically call the correct constructor for you automatically creating any objects the constructor depends upon (in your case class A).

A nice discussion can be found here: Why do I need an IoC container as opposed to straightforward DI code?

Community
  • 1
  • 1
Erik Schierboom
  • 16,301
  • 10
  • 64
  • 81
1

IMO, your question demonstrates that you understand the pattern.

Used correctly, you would have a Composition Root where all dependencies are resolved and injected. Use of an IoC container here would resolve dependencies and pass them down through the layers for you.

This is in direct opposition to the Service Location pattern, which is considered by many as an anti-pattern.

Use of a Composition Root shouldn't make your code less readable/understandable as well-designed classes with clear and relevant dependencies should be reasonably self-documenting. I'm not sure about unit testing a Composition Root. It has a discreet role so it should be testable.

David Osborne
  • 6,436
  • 1
  • 21
  • 35