Sponsored By Aspose - File Format APIs for .NET

Aspose are the market leader of .NET APIs for file business formats – natively work with DOCX, XLSX, PPT, PDF, MSG, MPP, images formats and many more!

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 :)

This entry was posted in Agile, Design, Test-Driven Development. Bookmark the permalink. Follow any comments here with the RSS feed for this post.

Leave a Reply