Table of Contents

Type - Dependency Resolve (Dependency Resolution)

About

dependency wiring

Wiring everything together is a tedious part of application development.

The process of finding an instance of a type dependency to use at run time is known as resolving the dependency

Method

There are several approaches to connect data, service, and presentation classes to one another.

The object:

Method Decoupling level Building Unit test Dependency Validation Description
constructor 1 Object Mock/cleanup Compile Time Constructors are more concise but restrictive. If a programmer initially elects to use a constructor but later decides that more flexibility is required, the programmer must replace every call to the constructor.
factory 2 Object Mock/cleanup Compile Time Factories decouple the client and implementation to some extent but require boilerplate code.
service_locator 3 Object Mock/cleanup Run Time Service locators decouple even further but reduce compile time type safety.
dependency_injection 4 External Mock Compile Time

All first three approaches inhibit unit testing.

Constructor

Direct constructor calls: Invoke a constructor, hard-wiring an object

With the below example, there is testing problems:

public class RealBillingService implements BillingService {
  public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
    CreditCardProcessor processor = new PaypalCreditCardProcessor();
    TransactionLog transactionLog = new DatabaseTransactionLog();

    try {
      ChargeResult result = processor.charge(creditCard, order.getAmount());
      transactionLog.logChargeResult(result);

      return result.wasSuccessful()
          ? Receipt.forSuccessfulCharge(order.getAmount())
          : Receipt.forDeclinedCharge(result.getDeclineMessage());
     } catch (UnreachableException e) {
      transactionLog.logConnectException(e);
      return Receipt.forSystemFailure(e.getMessage());
    }
  }
}

Factory

Design Pattern - (Static) Factory - A factory class decouples the client and implementing class.

In the code, we just replace the new constructor calls with factory lookups.

public class RealBillingService implements BillingService {
  public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
    CreditCardProcessor processor = CreditCardProcessorFactory.getInstance();
    TransactionLog transactionLog = TransactionLogFactory.getInstance();

    try {
      ChargeResult result = processor.charge(creditCard, order.getAmount());
      transactionLog.logChargeResult(result);

      return result.wasSuccessful()
          ? Receipt.forSuccessfulCharge(order.getAmount())
          : Receipt.forDeclinedCharge(result.getDeclineMessage());
     } catch (UnreachableException e) {
      transactionLog.logConnectException(e);
      return Receipt.forSystemFailure(e.getMessage());
    }
  }
}

The factory makes it possible to write a proper unit test. Each test will have to mock out the factory and remember to clean up after itself.

public class RealBillingServiceTest extends TestCase {

  private final PizzaOrder order = new PizzaOrder(100);
  private final CreditCard creditCard = new CreditCard("1234", 11, 2010);

  private final InMemoryTransactionLog transactionLog = new InMemoryTransactionLog();
  private final FakeCreditCardProcessor processor = new FakeCreditCardProcessor();

  @Override public void setUp() {
    TransactionLogFactory.setInstance(transactionLog);
    CreditCardProcessorFactory.setInstance(processor);
  }

  @Override public void tearDown() {
    TransactionLogFactory.setInstance(null);
    CreditCardProcessorFactory.setInstance(null);
  }

  public void testSuccessfulCharge() {
    RealBillingService billingService = new RealBillingService();
    Receipt receipt = billingService.chargeOrder(order, creditCard);

    assertTrue(receipt.hasSuccessfulCharge());
    assertEquals(100, receipt.getAmountOfCharge());
    assertEquals(creditCard, processor.getCardOfOnlyCharge());
    assertEquals(100, processor.getAmountOfOnlyCharge());
    assertTrue(transactionLog.wasSuccessLogged());
  }
}

Problem:

Service locator

Design Pattern - Service Locator

With the service locator pattern, the clients needs to know about the system they use to find dependencies

This is the same as factory but with the dependency verification/wiring that happens at runtime.

Dependency injection

Design Pattern - Dependency Injection.

The dependency injection pattern leads to code that's modular and testable

The class is not responsible for looking up the dependency. Instead, they're passed in as constructor parameters. The dependency is therefore exposed in the signature.

public class RealBillingService implements BillingService {
  private final CreditCardProcessor processor;
  private final TransactionLog transactionLog;

  public RealBillingService(CreditCardProcessor processor, 
      TransactionLog transactionLog) {
    this.processor = processor;
    this.transactionLog = transactionLog;
  }

  public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
    try {
      ChargeResult result = processor.charge(creditCard, order.getAmount());
      transactionLog.logChargeResult(result);

      return result.wasSuccessful()
          ? Receipt.forSuccessfulCharge(order.getAmount())
          : Receipt.forDeclinedCharge(result.getDeclineMessage());
     } catch (UnreachableException e) {
      transactionLog.logConnectException(e);
      return Receipt.forSystemFailure(e.getMessage());
    }
  }
}

Test:

public class RealBillingServiceTest extends TestCase {

  private final PizzaOrder order = new PizzaOrder(100);
  private final CreditCard creditCard = new CreditCard("1234", 11, 2010);

  private final InMemoryTransactionLog transactionLog = new InMemoryTransactionLog();
  private final FakeCreditCardProcessor processor = new FakeCreditCardProcessor();

  public void testSuccessfulCharge() {
    RealBillingService billingService
        = new RealBillingService(processor, transactionLog);
    Receipt receipt = billingService.chargeOrder(order, creditCard);

    assertTrue(receipt.hasSuccessfulCharge());
    assertEquals(100, receipt.getAmountOfCharge());
    assertEquals(creditCard, processor.getCardOfOnlyCharge());
    assertEquals(100, processor.getAmountOfOnlyCharge());
    assertTrue(transactionLog.wasSuccessLogged());
  }
}

But now, the clients of BillingService need to construct/lookup its dependencies.

 public static void client() {
    CreditCardProcessor processor = new PaypalCreditCardProcessor();
    TransactionLog transactionLog = new DatabaseTransactionLog();
    BillingService billingService = new RealBillingService(processor, transactionLog);
    ...
  }

This can be fixed :

Dependency Injection Framework

Instead of the programmer calling a constructor or factory, a tool called a dependency injector passes dependencies to objects.

Steps:

Moreover, the injector can verify that all dependencies have been satisfied at build time.

class Stopwatch {
 final TimeSource timeSource;
 @Inject Stopwatch(TimeSource TimeSource) {
   this.TimeSource = TimeSource;
 }
 void start() { ... }
 long stop() { ... }
}

The injector further passes dependencies to other dependencies until it constructs the entire object graph.

For instance with this class:

/** GUI for a Stopwatch */
class StopwatchWidget {
 @Inject StopwatchWidget(Stopwatch sw) { ... }
 ...
}

The injector might:

In unit tests, the programmer can now construct objects directly (without an injector) and pass in mock dependencies. The programmer no longer needs to set up and tear down factories or service locators in each test. This greatly simplifies our unit test:

void testStopwatch() {
 Stopwatch sw = new Stopwatch(new MockTimeSource());
 ...
}

Injector implementations can take many forms. An injector could configure itself using:

An injector could rely on:

An injector that uses compile-time code generation may not even have its own run time representation. Other injectors may not be able to generate code at all, neither at compile nor run time.

Documentation