Scott Bellware [MVP]

Sponsors

The Lounge

Syndication

Advertisement

Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
Dependency Patterns: Optional Dependencies and Primal Dependencies

If you wanted to get your chocolate in my peanut butter, would you want to do it via a constructor argument, or a setter?  It really depends on whether you believe that my peanut butter should not be without your chocolate, or if it can have an existence independent of the chocolate, but still get stuck with the chocolate when you just gotta get some of that peanut buttery chocolate goodness in a single bite.

The continuing success of a Reese' Peanut Butter Cup could be at some serious risk if Hershey's decided that it could optionally leave the peanut butter out of the chocolate cup.  In the case of a shippable Peanut Butter Cup, the peanut butter is probably a constructor argument rather than a setter.

Setter injection is often used to express optional dependencies.  Constructor arguments are primal dependencies.

Primal Dependencies

Constructor arguments are primal dependencies - the owning class can't live without them, and they're passed to the initializing function (the constructor in .NET speak) whenever an instance of the class is constructed.  If an instance of a class absolutely positively cannot be allowed to be constructed without its dependencies in place, then those dependencies have a primacy to the class.  A peanut butter cup without the peanut butter is nonsensical, much like an Oreo cookie with a missing center is nonsensical.

If a data access helper class should not be instantiated without having been provided a database connection, then the connection object is a primal dependency of the data access class.  And if a business service class could not be instantiated without having been provided an instance of the data access class, then the data access class is a primal dependency of the business service class.

DataAccess Class
public class DataAccess : IDataAccess
{
    
private readonly IDbConnection _connection;

    
public DataAccess(IDbConnection connection)
    {
        _connection = connection;
    }
}

 

BusinessService Class
public class BusinessService
{
    
private IDataAccess _dataAccess;

    
public BusinessService(IDataAccess dataAccess)
    {
        _dataAccess = dataAccess;
    }
}

There's no way to instantiate BusinessService except by satisfying its primal dependency on an instance of an IDataAccess.  There's no way to instantiate DataAccess except by satisfying its primal dependency on  an instance of an IDbConnection.  So there's no way to get a BusinessService instance without having all the primal ducks in a row:

string connectionString =
    
ConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString;

IDbConnection connection = new SqlConnection(connectionString);

DataAccess dataAccess = new DataAccess(connection);

BusinessService businessService = new BusinessService(dataAccess);

An IoC container would take care of the dependency stuff and pare down the above code to something along the lines of:

BusinessService businessService = Container.Resolve<BusinessService():

Oddly, a SqlConnection class can be created without providing a connection string.  As far as the semantics of the SqlConnection class goes, the connection string is an optional dependency.  I can't really think of a use case for this, but I'm sure it's out there somewhere.  Some customer must have asked the Base Class Library designers for this otherwise they wouldn't have invested in its perpetual maintenance...

Optional Dependencies

Since a dependency that is assigned using a setter can only be assigned to an instance of a class that has already come to life, then that class can exist independently of having its dependency set.  Because an instance can come to life without having the dependency set, the dependency is optional.  That's not to say that an instance of the owner class is useful or sensible without having the optional dependency set, but the dependency is inevitably optional.

Optional dependencies are secondary or ancillary to the primary concern of the class (yeah, ancillary is more Doctor Speak, but it's a useful word for describing what's going on here) .  An optional dependency often provides supporting services or infrastructural services to the owner.  In the BusinessService and DataAccess example above, logging would be an example of an ancillary service.  An instance of ILogger on either the BusinessService class or the DataAccess class from the example would likely be an optional dependency, and it would be implemented as a setter:

BusinessService with ILogger Optional Dependency
public class BusinessService
{
    
private IDataAccess _dataAccess;
    
private ILogger _logger;

    
public ILogger Logger
    {
        
set { _logger = value; }
    }


    
public BusinessService(IDataAccess dataAccess)
    {
        _dataAccess = dataAccess;
    }
}

Null Objects, Default Objects and Optional Dependencies

Optional dependencies go hand-in-hand with a Null Object or some other kind of Default Object pattern.  Default Object is usually implemented as some kind of service locator: a singleton or a call to a registry or factory.

A Default Object might be more meaningful for you can make a reasonably sounds decision ahead of time about the default logger that should be used in case the optional logger dependency isn't set.

Default Objects are often assigned using a service locator or registry.  Using Castle Windsor to provide the default logger for BusinessService would look something like:

BusinessService with Default Object Logger
public class BusinessService
{
    
private IDataAccess _dataAccess;
    
private ILogger _logger = Container.Resolve<ILogger>();

    
public ILogger Logger
    {
        
set { _logger = value; }
    }

    
public BusinessService(IDataAccess dataAccess)
    {
        _dataAccess = dataAccess;
    }
}

The default object implementation for ILogger in the example above has been assigned the logger that is configured in the container for this application.  It might also be assigned to a concrete logger implementation registered in the container, some other kind of registry, a singleton, or it could very well be a plain-old opaque dependency.

Here's the BusinessService class with the Null Object logger from the Castle project's Castle.Core.Logging namespace:

BusinessService with Null Object for Logger
public class BusinessService
{
    
private IDataAccess _dataAccess;
    
private ILogger _logger = NullLogger.Instance;

    
public ILogger Logger
    {
        
set { _logger = value; }
    }

    
public BusinessService(IDataAccess dataAccess)
    {
        _dataAccess = dataAccess;
    }
}

Unless a null reference exception means something significant to your application logic, an optional dependency shouldn't leave the door open for null reference exceptions.  The null logger ensures that any calls made on the logger don't result in a null reference exception.  The null logger doesn't actually do any logging.  It's just an implementation of Castle.Core.Logging.ILogger whose methods don't actually do anything.

The null object is often the more fictionless of the two default object patterns - especially for unit testing.  An instance of BusinessService constructed in a unit test will have a default object assigned to it's ILogger dependency.  If a real logger is assigned as a Default Object, then a real logger will be in play in the test.  If you're not testing logging, then the logger is excess baggage.  If the logger requires some kind of setup, or slows the execution of the unit test suite because of I/O, then it's real, tangible friction.

A Null Object logger will let you instantiate the owner and use it without any concern as to the effect of calls made to the logger in the course of the execution of the class's primal logic.  This is typically the situation we're in when unit testing.

Overloaded Constructors, Greedy Constructors, and Omission Constructors

If the owner class has overloads of the constructor that allow the omission of primal dependencies, then you shouldn't be surprised if you need to provide setters for the omitted dependencies.  You also shouldn't be surprised if you don't need to provide the setters :)

If you're using a dependency injection framework, then you can probably get by with providing a single greedy constructor.  A greedy constructor is one that accepts all of a class's primal dependencies.

Overloaded constructors that are included for the purposes of omitting dependencies are often used to facilitate testing.  If omission constructors are added to your class, and if you don't provide setters for the omitted dependencies, then default objects for the omitted dependencies are practically obligatory (in the sense that nothing is really obligatory, but some things are significantly north of just recommended :)

Omitted Dependencies

This section omitted for brevity.  See above for supporting material :)

API Solubility

The dependencies that you choose to assign with constructor-injection as primal dependencies and those that you chose to assign with setter injection are optional dependencies ends up communicating a lot about a class to a user.

By assigning the dependencies that are of the utmost importance to the meaning of the class with constructors, you're using the constructor to communicate the class's interest and requirements to other programmers using a clear and well-known medium.  This kind of information is critically important to people who want to use the class that you contributed to the project - it should leap off the screen and be immediately be absorbed by the user, without forcing him to stop and think about any setup that might be needed in the case of opaque dependencies.

The explicitness of constructor-injected dependencies is a service that you pay to the programmers that come after you.  However, it might not be very interesting to know whether a class like BusinessService or DataAccess supports logging.  It's helpful to move those ancillary dependencies to setters to make the primal dependencies more obvious.

Wrap-Up

Jeepers!  Who would have thought there would be so much to talk about just to get some peanut butter into a chocolate cup.

The good news is that you don't really have to think about dependencies enough to need a pattern language to facilitate an understanding of the problem and make dependency injection a bit more self-evident.

You could just use plain-old opaque dependencies and be done with.  You really only need to know this stuff if you want to do unit testing in an arguably effective way.  If you're not into testing though, I've got a case of empty chocolate cups I'd like to sell you under the guise of transacting over the actual peanut butter-filled kind :)


Posted Thu, Jun 28 2007 2:51 AM by ScottBellware

[Advertisement]