Type - Dependency Resolve (Dependency Resolution)

Card Puncher Data Processing

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:

  • testing the code will charge a credit card
  • what if the charge is declined
  • what if the service is unavailable.
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:

  • dependencies are hidden in the code. If we add a dependency (says a CreditCardFraudTracker), we have to re-run the tests to find out which ones will break.
  • the mock is a global variable:
    • If the tearDown fail, the global variable continues to point at our test instance causing problems for other tests.
    • it prevents tests to be run in parallel.

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:

  • does not need a setup and teardown.
  • will break at compile time
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 :

  • for intermediate class, by applying the pattern again
  • for top-level classes, by using a framework

Dependency Injection Framework

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

Steps:

  • Programmers annotate constructors, methods, and fields to advertise their injectability.
  • A dependency injector identifies a class's dependencies by inspecting these annotations, and injects the dependencies at run time.

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:

  • Find a TimeSource
  • Construct a Stopwatch with the TimeSource
  • Construct a StopwatchWidget with the Stopwatch

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:

  • XML, annotations,
  • a DSL (domain-specific language),
  • or even plain code.

An injector could rely on:

  • reflection
  • or code generation.

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





Discover More
Card Puncher Data Processing
Class - Dependency

in class (class as a type definition) Many types depend on other types. A dependency is any other object the current object needs to hold a reference to. The types on which a type depends are known...
Card Puncher Data Processing
Design Pattern - (Static) Factory

The Factory pattern creates an instance of an object according to a given specification, sometimes provided as arguments, sometimes inferred. It's a dependency resolving approach. A factory class decouples...
Card Puncher Data Processing
Design Pattern - Dependency Injection

Dependency injection is: a dependency resolution mechanism where components (object, bean, client) are given their (type) dependencies (service, ..) (Dependencies are injected) and therefore are...
Card Puncher Data Processing
Object - Constructor

A constructor is a method from a class that instantiate (build) an object. See also the method of object construction (ie dependency resolution) A static constructor is a static method that wrap...
Card Puncher Data Processing
Object - Relationship

in class (code). entity An Association is a structural relationship (a tuple) between the objects of two classes. A containment may be one of this form: composition - the building of object...
Card Puncher Data Processing
What is the Inversion of Control ?

Inversion of Control (IoC) is a design pattern that addresses a component’s: dependency resolution (dependency injection), configuration lifecyle It suggests...



Share this page:
Follow us:
Task Runner