Examining the Validity of Inversion of Control

February 2005

Discuss this Article


Inversion of Control (IOC) through injection also known as Injection IOC has not been a designer or programmer friendly pattern. Many question its validity and the validity of IOC in general. IOC seems to be a contradiction to the fundamental concept of object encapsulation. Context IOC is a new approach that attempts to capture Inversion of Control as a pure design pattern to demonstrate that IOC is indeed a very powerful concept.

Overview of IOC

Inversion of Control (IOC) is a new pattern that has been gaining popularity recently. The pattern is based on a few simple concepts that deliver a highly decoupled, lightweight, mobile, and unit-testable code base. The core concept behind IOC is that an object exposes its dependencies via some contract. Dependencies include such things as object implementations, system resources etc.., essentially anything that an object needs to perform its designated function but is not concerned with its implementation. In a nested object graph, each object in the call chain exposes its dependencies to the outer caller that uses it, who in turn exposes those dependencies including any of its own to its caller and so on -- until all dependencies manifest itself at the top. The top-object then assembles the dependency graph before activating the objects. The top-object is generally an entry point into your system such as an application main, Servlet, or even an EJB.

Validity of IOC

IOC seems to be at odds with the fundamental paradigm of object encapsulation. The concept of upstream propagation of dependencies is indeed a contradiction to encapsulation. Encapsulation would dictate that a caller should know nothing of its helper objects and how it functions. What the helper objects do and need is their business as long as they adhere to their interface - they can grab what they need directly whether it be looking up EJBs, database connections, queue connections or opening sockets, files etc.. Of course, we are now discovering that this strict interpretation of encapsulation is in itself invalid. It forces us to come up with contrived solutions for managing functionality and resources across object boundaries, jumping through all sorts of hoops - such as managing thread locals for transactions, security, user info, caching etc.. Whether these hoops are jumped for us by an EJB container or contrived within the application - we have to come to terms with the fact that the paradigm of encapsulation has to be eased up a bit. Encapsulation has to be eased up to the point where any dependency that is managed or has the potential to be managed across object boundaries must be exposed for higher management. Not much different from a corporation where an employee's purchase request for a PC propagates up though management. What should be exposed is not just limited to usages of managed resources but include usages of managed functionality as well. Therefore, the upstream propagation of dependencies to a top-object via IOC is indeed valid. The top-object knows its environment - how to obtain and manage shared resources, configurations and functionality for a usecase. In essence, we need a paradigm shift from strict encapsulation to exposing of managed objects via IOC. Encapsulation is still an important concept and easing it a little by no way means throwing it out the window. Besides cross boundary managed objects there is an additional driving force toward IOC, namely isolation of concerns. This is an old object-oriented mantra that is delivered to its full potential with IOC. Objects can focus on intended functionality and leave implementations of unintended concerns to higher authorities via IOC.

Dimensions of IOC

There are 2 dimensions along which IOC can be applied within your application. The first dimension is a horizontal or breadth-wise dimension where at the extreme you would have all objects decoupled from each other and the top-object assembles a highly complex object graph and manages the life-cycle of all objects. The second dimension is a vertical or depth-wise dimension where at the extreme you would have objects highly coupled except for the most heavyweight of resource usages which will be assembled by the top-object. As usual the best design is somewhere in between where most objects are decoupled, but then there are also intermediary objects that assemble a part of the object graph to provide concrete and encapsulated units - much like a corporation with multiple layers of management leading up to the CEO.

Benefits of IOC

The benefits of IOC are that objects become highly focused in its functionality, highly reusable, and most of all highly unit testable. Cross boundary management of resources and functionality becomes straightforward. Objects are not coupled directly to environment resources or other unintended implementations. A unit test for an object can easily mock up its dependencies and then unit test that object's logic explicitly in isolation. These tests are easier to write and lightening fast. Most unit tests would NOT require you to mock up database connections and JNDI trees which can result in slow and cumbersome tests. Mind you, mocking up database connections have their place in integration tests - but not for the 90% of logic there is to unit test.

Injection IOC

Injection IOC is a popular approach to IOC that advocates that an object expose its dependencies via its constructor or Java bean properties. This popular use of Injection IOC also includes a generic container that manages the overall object graph and the injection of dependencies when an object's life-cycle begins. I refer to the container as generic because it can be used with any application as it reads your object graph from a descriptor file and then proceeds to manage it.

Problems with Injection IOC

The problem with exposing dependencies via constructors or Java bean properties in general is that it clutters an object and overwhelms the real functional purposes of the constructor and Java bean properties. It also forces the caller to perform a lot of assembly i.e glue work which cannot be easily captured, reused and extended. Generic containers attempt to capture the glue work in descriptor files such as XML that are loaded and assembled by the container. They gear applications toward an extreme horizontal dimension of IOC with superfluous decoupling and complex object graphs exposed as untyped descriptor files which are not easily extensible and do not provide any compile time verification. Of course externalizing environmental dependencies such as resource bindings etc.. into XML deployment descriptors as is done with J2EE containers is another matter, this type of externalizing is necessary, but externalizing an entire object graph seems over ambitious and counter productive. This type of glue work is considered core functionality and may be subject to business requirements and testing procedures and exposing it as an untyped descriptor file can make the application very brittle. Finally, this core functionality type glue work should be reusable and extensible just like any other part of the application that represents the business domain.

Context IOC

Injection IOC, even though a step forward in design, does not do IOC complete justice for the reasons I have already mentioned. The concept of a generic container managing your core usecase flow diminishes IOC as a design pattern decreasing its accessibility for general purpose use - after all design patterns should only need language expression. Additionally, people are thrown off by the injection clutter and most of all by the "perception" that IOC implies ZERO encapsulation.

Context IOC on the other hand is Inversion of Control at its purest - just a pattern for design without any baggage. It eliminates clutter and promotes encapsulation. It provides a clean way to organize an object's contextual dependencies in the form of a Context interface declared within its class. Inversion of Control is then further expressed through Context Extension.

Example-1 : Pool Game

The PoolGame class expresses its dependencies via an inner interface called a Context and then takes an instance of the Context in its constructor. The class performs its encapsulated logic to play a pool game but delegates to its Context functionality such as draw() whose implementations do not concern the PoolGame. Note that later refactoring of the Context interface may move some of the Context's methods to other interfaces - one of the benefits of using Context IOC as seen later.

public class PoolGame {

  public interface Context {
    public String getApplicationProperty(String key);
    public void draw(Point point, Drawable drawable);
    public void savePoolGame(String name, PoolGameState poolGameState);
    public PoolGameState findPoolGame(String name);
  }

  public PoolGame(Context cxt) {
    this.context = cxt;
  }

  public void play() {
    //start play
  }

  public void endPlay() {
    //end play
  }

  public void savePlay(String name) {
    PoolGameState saveGameState = new PoolGameState(this.gameState);
    context.savePoolGame(name, saveGameState);
  }

  public void play(String savedPoolGameName) {
    PoolGameState savedGameState = context.findPoolGame(savedPoolGameName);
    gameState = new PoolGameState(savedGameState);
    play();
  }

  private final Context context;
  private PoolGameState gameState;

}

The Context interface removes the clutter of exposing dependencies via constructors or Java bean properties (except Of course for the one context argument). It provides better clarity by organizing an object's dependencies into a single personalized interface unique to that class. The PoolGame.Context interface is unique to the PoolGame class. Now constructor arguments and properties can be reserved for what they are meant for i.e. varying an object's usable functionality, such as a gameType property that could control the type of game that is played such as 9-ball or Straight Pool.

Context Extension

Inversion of Control is expressed further through Context Extension. Contexts can extend other Contexts. A caller must implement its helper's Context or pass the contract further upstream by providing its own Context interface that extends its helper's Context. The caller then adds contextual dependencies of its own to its Context. In essence the caller is stating that its contextual needs include everything from its helper as well as some of its own. This approach of extending Contexts to propagate dependencies is perfectly valid between objects that collaborate with each other e.g. services and their helpers. The alternative approach would be for the caller to declare a dependency to the callee's interface within its Context. This decouples the caller from callee's implementation. The top assembler then implements both the caller and callee's Contexts and chooses the correct implementation of the callee for the caller. This approach would be used to decouple large grained objects and resource consumers from each other such as services from other services. In essence Context Extension is used by intermediary assemblers that represent a concrete encapsulated large grained functional unit. They glue together concrete implementations of helper objects to achieve this large grained functionality and then propagate upstream all dependencies of its helpers via Context Extension. These large grained units are just as unit testable as its helpers and should have their own detailed unit tests that show their assembled functionality is in proper order. Its also important to note that in addition to assembling concrete helpers, these large grained units will often add more business logic in and around the assembly work.

Propagating dependencies with Injection IOC via constructors or Java bean properties forces modification to every class in the call-chain(s) to accommodate new dependencies unless you implement an extreme horizontal dimension of IOC which in turn would increase the clutter within each object. Introducing or modifying new dependencies with Context Extension impacts only the top-assembler's class(s) that assemble the large grained objects - since it is only they that implement the Contexts. Modifications to an object's Context will propagate upstream to the top via Context Extension without affecting intermediary classes in the call-chain(s).

Context interfaces provide a great way to abstract out dependencies into its own object hierarchy and design allowing for reuse and extensibility. Methods in a Context that represent similar issues can also be refactored into its own interface - the Context can then either extend this new interface or provide a getter to it. Even more importantly, implementations of Contexts i.e. glue code can be generalized and abstracted out for reuse and extension. Implementations of Contexts are generally provided by top-level classes i.e. usecase-entry points. Context implementations in whole or in parts can be abstracted out and shared across multiple usecases that have similar environments or requirements. Additionally, refactoring of Context implementations will identify configurables that should truly reside externally as application or deployment properties. Application properties typically being stored in a database for administrative applications while deployment properties residing in XML or properties files for the deployers.

In the end, all of this application glue is captured in extensible Contexts that are compile-time verified. Introduce or modify a dependency in an object's Context and a compile will verify all usecases that use that object and consequently implement some version of its contextual dependencies. On the other hand, generic IOC containers that read in object graphs from a descriptor and claim maximum configurability do not provide true ROI - they have captured essential application glue in an untyped descriptor file that cannot be understood or modified by application administrators or deployers. Furthermore, changes in the glue descriptors can modify core usecase flows and should go through proper testing before being released, therefore, it should not be as easily modifiable external to the application.

Example-2 : Matching jobs

The usecase of matching jobs to a candidate demonstrates the use of Context IOC, its integration with existing patterns to fulfill functionality, and finally how it is applied in enterprise deployment.

Service pattern with Context IOC

The Service pattern is used to represent and group usecases. Each method defined in a Service interface represents a usecase activated by the user or the system. It generally also represents a transaction of one or more resources. The Service's business functionality is implemented as a plain Java object and represents a large grained functional unit. It propagates upstream contextual needs of its helpers via Context Extension. Note that this unit adds business functionality in and around its assembly of its helpers and and so declares some contextual needs of its own.

/**
* The Service interface for matching Jobs.
*/
public interface JobMatchService {

  /**
   * Represents the usecase for matching jobs to a candidate.
   * @return Set : The set of Job objects.
   */
  public Set match(CandidateKey candidateKey);

  //...other usecase methods
}


/**
* The JobMatchService's business logic in a plain Java object.
*/
public class JobMatchServiceImpl
  implements JobMatchService
{
  /**
   * Declares what this impl needs in its Context interface.
   * In addition to defining its own needs in its Context, it extends
   * Contexts of helper classes that it uses, so as to propagate all
   * contextual needs upstream.
   */
  public interface Context
    extends MatchStrategyFactory.Context
  {
    public CandidateStore getCandidateStore();
    ... //other methods
  }

  /**
   * Note that the provided Context argument can be passed along as is to
   * all helper objects since the Context implements all their Contexts as
well.
   */
  public JobMatchServiceImpl(Context cxt) {
    this.context = cxt;
    this.matchStrategyFactory = new MatchStrategyFactory(cxt);
  }

  /**
   * Contains all business logic for the job matching usecase.
   */
  public Set match(CandidateKey candidateKey)
    throws CandidateNotValidException
  {
    Candidate candidate =
context.getCandidateStore().findCandidate(candidateKey);

    if (candidate == null || candidate.isRegistrationExpired()) {
      throw new CandidateNotValidException(candidateKey);
    }

    MatchStrategy strategy = matchStrategyFactory.createStrategy(candidate);
    Set jobs = strategy.match(candidate);

    candidate.setLastMatchedDate(new Date());
    candidate.setMatchStrategyUsed(strategy.getName());
    context.getCandidateStore().update(candidate);

    return jobs;
  }

  //...other usecase method implemenations

  private final Context context;
  private final MatchStrategyFactory matchStrategyFactory;
}
Strategy pattern with Context IOC (Example-2 continued)

The strategy pattern is used to determine the appropriate matching algorithm to apply under different circumstances. A factory creates the appropriate strategy and uses its Context and Context Extension to propagate requirements of the individual strategies. In this example we have a predetermined number of strategies and therefore each strategy can provide their own Contexts which are combined and propagated upstream via the factory's Context. However, in the cases where strategies need to be added dynamically - a single Context can be defined in a base strategy class that all sub-classes will need.

/**
* This factory creates the appropriate matching strategy for a given
candidate.
*/
public class MatchStrategyFactory {

  /**
   * Being a factory for all the strategies - it propagates all the
   * contextual needs of the underlying strategies it creates.
   */
  public interface Context
    extends JobMatchConfig.Context, QuickMatchStrategy.Context,
      SinglePhaseMatchStrategy.Context, TwoPhaseMatchStrategy.Context,
        MaximumPhaseMatchStrategy.Context
  {
    //...no methods since no contextual needs of its own.
  }

  public MatchStrategyFactory(Context cxt) {
    this.context = cxt;
    config = new JobMatchConfig(cxt);
  }

  /**
   * Create the strategy appropriate for the given candidate.
   * Note that the provided Context argument can be passed along as is to
   * all strategy objects since the Context implements all their Contexts
   * as well.
   */
  public MatchStrategy createStrategy(Candidate candidate) {
    //note: config was initialized in the constructor above...

    if (candidate.daysOnSearch() < config.getDaysOnSearchLow()) {
      strategy = new QuickMatchStrategy(context);
    }
    else if (candidate.daysOnSearch() < config.getDaysOnSearchMedium()) {
      strategy = new SinglePhaseMatchStrategy(context);
    }
    else if (candidate.daysOnSearch() < config.getDaysOnSearchHigh()) {
      strategy = new TwoPhaseMatchStrategy(context);
    }
    else{
      strategy = new MaximumPhaseMatchStrategy(context);
    }

    return strategy;
  }

  private final Context context;
  private final JobMatchConfig config;
}


/**
* Represents a strategy to match Jobs for a candidate.
*/
public interface MatchStrategy {
  public String getName();
  public Set match(Candidate candidate);
}


/**
* A Quick and dirty implementation of a MatchStrategy.
*/
public class QuickMatchStrategy
  implements MatchStrategy
{
  /**
   * Context declaring that it needs the Read Only version
   *   of the Job Persistent Store.
   *
   * Context vs. Constructor
   *   The advantage to placing things in the Context vs. Constructor
   *   are that this object's users need not be affected by what the object
   *   needs in order to implement itself.
   */
  public interface Context {
    public JobStore getJobStoreReadable();
  }

  /**
   * Resides in same package as MatchStrategyFactory and so only provides
   * package visibility for constructor.
   */
  QuickMatchStrategy(Context cxt) {
    this.context = cxt;
  }

  /**
   * Quick and dirty algorithm...
   * Finds jobs based on top skill only.
   */
  public Set match(Candidate candidate) {
    Set jobs =
context.getJobStoreReadable().findJobsBySkill(candidate.getTopSkill());

    Iterator jobsIter = jobs.iterator();
    while(jobsIter.hasNext()) {
      Job job = (Job)jobsIter.next();
      if (job.yearsOfExperience() > candidate.yearsOfExperience()) {
        if (job.salary() < candidate.salary()) {
           jobsIter.remove();
        }
      }
    }

    return jobs;
  }

  private final Context context;
}
Config pattern with Context IOC (Example-2 continued)

A simple pattern to access configuration within an application. Configuration is grouped by concern into classes which then provide an encapsulated well typed interface to individual properties. The Config pattern uses Context IOC to access application properties.

/**
* A helper to read and translate properties used for JobMatch.
*/
public class JobMatchConfig {

  /**
   * Context declaring that it needs an application property.
   *   Which may be stored in a Database or URL or local file-system
   *   as determined by the top usecase assembler.
   *
   * Context vs. Constructor
   *   The advantage to placing things in the Context vs. Constructor
   *   are that its callers can easily pass on the implementation
   *   requirements through Context Extension.
   */
  public interface Context {
    public String getApplicationProperty(String key);
  }

  public JobMatchConfig(Context cxt) {
    this.context = cxt;
  }

  public int getDaysOnSearchLow() {
    String key = "job.match.days.on.search.low";
    String value = context.getApplicationProperty(key);

    try {
      return Integer.parseInt(value)
    }catch(NumberFormatException x){
      throw new IllegalConfigException(key, value, x);
    }
  }

  public int getDaysOnSearchMedium() {
    //...similar to others
  }

  public int getDaysOnSearchHigh() {
    //...similar to others
  }

  ... //other methods

  private final Context context;
}
Store pattern with Context IOC (Example-2 continued)

The Store pattern is used for accessing persistent data. A common misuse of IOC is when business objects access DataSources for their specific data needs. They may have used IOC to access the DataSources which is partly good but misses the point. These business objects are not as reusable and not as easily unit testable - since unit testing them means mocking up DataSources for them. These objects should instead declare and expose their dependency to the data they need - allowing the top-level usecase assembler to decipher where it comes from. A good design would be to have these business objects declare a dependency to a Store interface that gives them a specific type of persistent data entity they need. This allows easy unit testing of these business objects since mocking up a Store and its mock data becomes easy and more importantly lightening fast. In general, unit testability of an object is a good measuring stick for good IOC designs.

The CandidateStore follows a similar pattern as shown.

/**
* Interface that identifies complete read / write to the
*   persistent Store for Job data.
*/
public interface JobStore
  extends JobStoreReadable
{
  public Job add(Job job);
  public Job update(Job job);
  public void addSkills(JobKey jobKey, Set skills);
  ... //other methods
}


/**
* Interface that identifies only the Readable interface to the
*   Job Persistent Store.
*
* Allows top-objects to optimize transactions if a usecase
*   flow requires only Readable Stores.
*/
public interface JobStoreReadable {
  public Job findJob(JobKey key);
  public Set findJobsBySkills(Set skills);
  ... //other methods
}


/**
* The implementation of reading and writing to the Job Persistent Store.
*/
public class JobStoreImpl
  implements JobStore
{
  /**
   * Context declares that it needs a JDBC Connection to the Database.
   *   This approach removes the burden/errors of Connection closing from all the objects.
   *   The burden is solely on the top-level usecase assembler to provideand then close the
   *   connection for a given usecase.  Allows top-level usecase assembler
to reuse
   *   Connections etc.. (See alternatives next).
   *
   * Alternative: Can declare DataSource instead of Connection.
   *   To allow for a more fine grained connection usage - but burden/errors
of Connection
   *   closing is left to each StoreImpl to handle themselves.
   *
   * Alternative: Can declare a HibernateSession instead of Connection.
   *   This approach removes the burden/errors of Connection and
HibernateSession closing from
   *   all the objects. The burden is left solely on the top-level assembler
to provide the
   *   HibernateSession and then close the Session and Connection for a
given usecase.
   *   Allows top-level usecase assembler to cache/reuse Sessions, reuse
Connections etc..
   *
   * Alternative: Can accept DataSource/Connection/HibernateSession via
Constructor.
   *   This is Not as extendable.  With a Context, callers can easily extend
and pass on the
   *   responsibility higher without really being affected by what the
StoreImpl needs.
   *   Also, the top-object need only implement this method once for all
StoreImpls used in
   *   the usecase and lazily create the connection.
   *
   * Optional: If multiple DataSources exist in your application or you'd
like to accommodate
   *   such a possibility.  You can clarify getConnection() to specify a
logical datasource name:
   *     as getConnection(String datasource)
   *   allowing the top-level usecase assembler to assemble the correct
Connection from the
   *   correct DataSource.
   */
  public interface Context {
    public java.sql.Connection getConnection();
  }

  public JobStoreImpl(Context cxt) {
    this.context = cxt;
  }

  public Job findJob(JobKey key) {
    ... //use Connection from context to find
  }

  public Set findJobsBySkills(Set skillset) {
    ... //use Connection from context to find
  }

  public Job add(Job job) {
    ... //use Connection from context to add
  }

  public Job update(Job job) {
    ...  //use Connection from context to update
  }

  public void addSkills(JobKey jobKey, Set skills) {
    ... //use Connection from context to add skills.
  }

  ... //other methods

  private final Context context;
}
Enterprise deployment with Context IOC (Example-2 continued)

Once a Service and its ServiceImpl(s) with its usecase methods and its business logic have been built using Context IOC, it can be plugged-into any environment. If you were to deploy on a J2EE server - you may choose to wrap your Service within an EJB or a Servlet depending on your auto-management needs for security, transaction, resources etc. If you choose to deploy it as part of a standard main() or batch type application then you may wrap your Service in an Application class. I like to think of EJBs, Servlets etc.. as merely entry points into a system that act as top-level usecase assemblers. They essentially take a Service and adapt its contextual needs to the environment that the entry point represents. The assemblers merely fulfilll the Service's Context and then delegate to the Service's usecase method to perform all the functionality associated with the usecase.

/**
* A Stateless SessionBean deployment of the JobMatchService.
* This EJB uses container-managed security and transactions.
*/
public class JobMatchServiceEJB
  implements SessionBean, JobMatchService
{
  /**
   * Assembles the JobMatchService's Context to perform the job match
usecase.
   * Treated as just an entry point for a type of environment - it knows how
to provide
   * security, manage transactions, and access the resources needed by the
useacase service.
   */
  public Set match(CandidateKey candidateKey)
    throws CandidateNotValidException
  {
    ServiceContext context = new ServiceContext(); //see private class
later.

    try {
      JobMatchService service = new JobMatchServiceImpl(context);
      return service.match(candidateKey);
    }finally{
      context.destroy();
    }
  }

  /**
   * A private implementation of the Service's Context used only by this
EJB.
   * If several EJB's need to share this Context or parts of it - it can be
   * extracted out and refactored for reuse.
   *
   * Thread safety:
   *   The ServiceContext holds state (for lazy initialization etc..) but
thread
   *   synchronization is not needed since its created and executed inside
   *   the EJB's method.
   */
  private class ServiceContext
    implements JobMatchServiceImpl.Context, CandidateStoreImpl.Context,
      JobStoreImpl.Context, AppConfigStoreImpl.Context
  {
    public CandidateStore getCandidateStore() {
      return new CandidateStoreImpl(this); //can be optimized as one
instance.
    }

    public JobStoreReadable getJobStoreReadable() {
      return new JobStoreImpl(this); //can be optimized as one instance.
    }

    /**
     * Obtains application properties from the database.
     * Can be modified to say read from a URL.
     */
    public String getApplicationProperty(String key) {
      //can be refactored for longer term caching.
      if (appConfig == null) {
        AppConfigStore configStore = new AppConfigStoreImpl(this);
        appConfig = configStore.findAppConfig("JobMatch");
      }
      return appConfig.getProperty(key);
    }

    /**
     * Used by all the StoreImpl classes that are part of a usecase.
     *
     * Only one instance of the Connection created and used for the single
     * execution of a usecase. This approach provides a one place easy
management
     * of a Connection for an entire usecase and is sufficient for most
scenarios.
     * The individual StoreImpls don't need to worry about closing
Connections.
     * Resource leaks are avoided with this approach.
     *
     * Alternative: If usecase has a lot of processing between Connection
usage its
     * Context should request a DataSource instead and manage its own
Connection
     * life-cycle.
     */
    public Connection getConnection() {
      try {

        if (conn == null) {
          DataSource ds = (DataSource)(new
InitialConext()).lookup("jdbc/job");
          conn = ds.getConnection();
        }
        return conn;

      }catch(Exception x){
        throw new InvalidStateException(x);
      }
    }

    /**
     * Clean up resources used by this Context.
     */
    public void destroy() {
      try {
        if (conn != null) {
          conn.close();
        }
      }catch(Exception x){
          log.warn(x);
      }
    }

    private Connection conn = null;
    private AppConfig appConfig = null;
  }

}
Unit tests with Context IOC (Example-2 continued)

Unit tests are merely another entry point into a system - more specifically a test system, and as such adapts a Service's contextual needs to the test system by mocking up implementations. JUnits can not only mock up the Service's Context but also the Contexts of any of its helpers or utilities. I prefer to create concrete mocks that implement the various Contexts or Stores directly rather than use the generic mock libraries that are freely available - but either will work. The concrete mocks look like actual classes that can themselves be generalized, analyzed and refactored for maximum reuse of your test data within your test system.

public class JobMatchServiceImplTest
  extends TestCase
{
  /**
   * A simple test for the job match service.
   */
  public void testMatch() {
    TestContext testContext = new TestContext(); //see private class later
    JobMatchServiceImpl jobMatchService = new
JobMatchServiceImpl(testContext);
    CandidateKey candidateKey = new TestCandidateKey("Sony");
    Set jobs = jobMatchService.match(candidateKey);
    assertNotNull(jobs);
    assertEquals(5, jobs.size());
    assertEquals(1, testContext.candidateStore.updateCount());
    //...other asserts.
  }

  /**
   * A private test context implementation used by this Test only.
   * But can be refactored for reuse by many tests.
   */
  private class TestContext
    implements JobMatchServiceImpl.Context
  {
      final TestCandidateStore candidateStore = new TestCandidateStore();
      final TestJobStore jobStore = new TestJobStore();

      public CandidateStore getCandidateStore() {
        return candidateStore;
      }

      public JobStoreReadable getJobStoreReadable() {
        return jobStore;
      }

      public String getApplicationProperty(String key) {
        if (key.equals("job.match.days.on.search.low") {
          return "7";
        }else if (key.equals("job.match.days.on.search.medium") {
          return "14";
        }else if (key.equals("job.match.days.on.search.high") {
	    return "25";
        }else{
          throw new IllegalStateException("Bad application property
requested " + key);
        }
      }

      public Connection getConnection() {
        throw new IllegalStateException("Connection request invalid in a
test case");
      }
  }
}

/**
* An e.g. of a mock Store used by many tests.
*
* A test case can choose to create its own test Store
* and assert directly in its methods OR use a shared test
* Store which can later be inspected and asserted on.
*/
public class TestCandidateStore implements CandidateStore {
  private final Collection candidates = new ArrayList(100);

  public TestCandidateStore() {
    add(new Candidate("Sony"));
    add(new Candidate("Roney"));
    add(new Candidate("James"));
    //... more data.
  }

  public void add(Candidate candidate) {
    candidates.add(candidate);
    //... add to various indexed maps
    //... for the find methods.
  }

  public Job findCandidate(CanidateKey key) {
    //lookup an indexed map to find.
  }

  public void update(Candidate candidate) {
    //lookup an indexed map to find.
    //then update the object or throw exception if not found.
    //track updates for later inspection
  }

  /**
   * E.g. method that allows inspection of Store to assert on.
   */
  public int updateCount() {
    //return count from update list.
  }

  //... other methods
}

Finally

Inversion of Control(IOC) is indeed a valid concept and the old fogies of OO design must come to terms with this natural evolution towards IOC. The concept of strict encapsulation must be eased to allow for cross boundary managed resources and functionality via IOC. Unit testability of isolated concerns as delivered by IOC is also a major leap forward in providing quality software. Injection IOC though a step forward in design does not deliver on IOC's full potential and Context IOC attempts to deliver where Injection IOC falls short - as a general purpose design pattern.

Author Bio

Sony Mathew
Lead Architect, Prime Therapeutics, MN 55121
smathew@primetherapeutics.com

Sony Mathew is the Lead Architect at Prime Therapeutics located in Minnesota, formulating the direction of its enterprise applications comprised primarily of delivering pharmacy products and services through web-sites and through direct B2B integration with web-services. He has about 6 years of experience developing and designing J2EE applications.

PRINTER FRIENDLY VERSION