SOLID principles in .NET revisited part 7: the Dependency Inversion Principle

Introduction

In the previous post we saw the definition of the Interface Segregation Principle. We applied it to a problematic case where a class could not fully implement the IAuthorizationService interface. We then broke up the interface into two parts so that they became more specialised. A consequence of ISP is often a large number of small, very specialised interfaces of 1 or maybe 2 methods. Large, monolithic interfaces are to be avoided as it will be more difficult to find concrete classes that can meaningfully implement all interface methods.

We’ve reached the last letter in the SOLID acronym, i.e. ‘D’ which stands for the Dependency Inversion Principle.

DIP

The DIP states that classes should depend on abstractions for their dependencies. DIP aims to reduce coupling between objects. I personally think the DIP is the largest topic of the 5 within SOLID. There’s a series devoted to DIP on this blog starting here with lots of details: dependency injection, inversion of control, DIP patterns, DIP antipatterns etc. It’s a large topic and this post is only a refresher. It’s not much use going through that material over again in the same depth.

The easiest example of the lack of DIP and tight coupling is the following declaration:

private LoggingService _loggingService;

public OnDemandAgentService()
{
	_loggingService = new LoggingService();
}

Why is this bad? The OnDemandAgentService is tightly coupled to the LoggingService object. Also, LoggingService is not an abstraction which makes matters even worse. It’s very difficult to supply a different implementation of the logging mechanism without changing the code manually within OnDemandAgentService.

The solution is to first hide the LoggingService class behind an interface:

public interface ILoggingService
{
	void LogInfo(string info);
	void LogWarning(string warning);
	void LogError(string error);		
}

…and make LoggingService implement the interface:

public class LoggingService : ILoggingService

Then we can declare the type of the private _loggingService field in OnDemandAgentService to be ILoggingService:

private ILoggingService _loggingService;

public OnDemandAgentService()
{
	_loggingService = new LoggingService();
}

Is this enough? Not at all, the OnDemandAgentService class is still tied to LoggingService in the constructor. How can we make sure that the client can – and must – provide an implementation of ILoggingService?

ILoggingService is a dependency that OnDemandAgentService needs in order to perform its job. It can create a new one using the “new” keyword like above in a control-freak fashion: I know what I want and I’ll do it myself. Clients have no way of interfering with that. OnDemandAgentService has to trust the client that it will provide the necessary ingredients. OnDemandAgentService will have to open up and provide an entry point for the client. It’s the client’s responsibility to supply a valid concrete implementation for the dependency.

The most straightforward way for such an entry point is a technique called constructor injection. It’s a scary name but all it really means is that we extend the class constructor with parameters for the necessary dependencies. Here’s an example for the LoggingService:

private ILoggingService _loggingService;

public OnDemandAgentService(ILoggingService loggingService)
{
	_loggingService = loggingService;
}

The clients of OnDemandAgentService will now absolutely have to provide an object which implements ILoggingService when building an OnDemandAgentService object.

There’s one remaining problem. A client can pass in a null which will cause an exception to be thrown in the StartNewOnDemandMachine method. We can add a guard clause to prevent that:

public OnDemandAgentService(ILoggingService loggingService)
{
	if (loggingService == null) throw new ArgumentNullException("Logging service");
	_loggingService = loggingService;
}

Cleanup according to the DIP

Looking through the current state of OnDemandAgentService we can locate the following dependencies:

  • ILoggingService
  • ICloudProvider
  • IAuthorizationService
  • IUnauthorizedAccessPunishmentService
  • EmailService

We have no abstraction for EmailService yet, let’s remedy that problem quickly:

public interface IEmailService
{
	void SendEmail(string message, string recipient, string emailHost);
}

public class EmailService : IEmailService
{
	public void SendEmail(string message, string recipient, string emailHost)
	{
		//implementation ignored
	}
}

Here’s the updated form of OnDemandAgentService according to the constructor injection technique:

public class OnDemandAgentService
{
	private ILoggingService _loggingService;
	private ICloudProvider _cloudProvider;
	private IAuthorizationService _authService;
	private IUnauthorizedAccessPunishmentService _punisher;
	private IEmailService _emailService;

	public OnDemandAgentService(ILoggingService loggingService, ICloudProvider cloudProvider, IAuthorizationService authService
		, IUnauthorizedAccessPunishmentService punisher, IEmailService emailService)
	{
		if (loggingService == null) throw new ArgumentNullException("Logging service");
		if (cloudProvider == null) throw new ArgumentNullException("Cloud provider");
		if (authService == null) throw new ArgumentNullException("Authorization service");
		if (punisher == null) throw new ArgumentNullException("UnauthorizedAccessPunishmentService");
		if (emailService == null) throw new ArgumentNullException("Email service");
		_loggingService = loggingService;
		_cloudProvider = cloudProvider;
		_authService = authService;
		_punisher = punisher;
		_emailService = emailService;
	}		

	public OnDemandAgent StartNewOnDemandMachine()
	{
		_loggingService.LogInfo("Starting on-demand agent startup logic");
		try
		{
			if (_authService.IsAuthorized(Username, Password))
			{
				_loggingService.LogInfo(string.Format("User {0} will attempt to start a new on-demand agent.", Username));
				OnDemandAgent agent = _cloudProvider.StartServer();
				_emailService.SendEmail(string.Format("User {0} has successfully started a machine with ip {1}.", Username, agent.Ip), "admin@mycompany.com", "email.mycompany.com");
				return agent;
			}
			else
			{
				bool lockedOut = _punisher.LockOut(Username);
				_loggingService.LogWarning(string.Format("User {0} attempted to start a new on-demand agent. User locked out: {1}", Username, lockedOut));
				throw new UnauthorizedAccessException("Unauthorized access to StartNewOnDemandMachine method.");
			}
		}
		catch (Exception ex)
		{
			_loggingService.LogError("Exception in on-demand agent creation logic");
			throw ex;
		}
	}

	public string Username { get; set; }
	public string Password { get; set; }
}

All clients of OnDemandAgentService are now obliged to provide implementations for all the dependencies of OnDemandAgentService. OnDemandAgentService in turn can be happy as it can now concentrate on its tasks without worrying about creating concrete implementations for the required dependencies.

Also, notice that all instances of the “new” keyword are gone from OnDemandAgentService except where an exception is thrown. As soon as you new up an object which is a dependency you tie your object to that concrete dependency.

Finally it’s important to note that we only have a single constructor which accepts all dependencies. Resist the temptation of creating overloaded constructors where you’ll need to provide some default implementation of a dependency like this:

public OnDemandAgentService(ILoggingService loggingService, ICloudProvider cloudProvider, IAuthorizationService authService)
	: this(loggingService, cloudProvider, authService, new DatabaseAuthorizationService(), new EmailService())
{

}

You might just as well declare your dependencies directly in the constructor with the “new” keyword.

Inversion of control containers

I will only briefly mention IoC containers. If you don’t know what they are then it’s enough to know that they are mechanisms to automatically – automagically… – inject the object dependencies through object constructors or methods. They tend to have quite a complex implementation but it’s usually hidden. A popular IoC container in .NET is StructureMap. StructureMap needs to be initialised at the start of the application’s life cycle where you declare the concrete implementations to use for the dependencies. Here’s an example:

ObjectFactory.Initialize(x =>
{
	x.For<IUnitOfWork>().Use<EntityFrameworkUnitOfWork>();
	x.For<IRoleProvider>().Use<AspNetRoleProvider>();
        x.For<IConfigurationRepository>().Use<AppSettingsConfigurationRepository>();
});
ObjectFactory.AssertConfigurationIsValid();

If you’re not familiar with IoC containers this may look cryptic and it’s probably difficult to understand how this works in a real application. It’s fine, you’ll come into contact with IoC containers when working seriously with DIP.

There’s a single point I want to make here. IoC containers usually allow you to extract the declared implemented type anywhere in your application. In StructureMap you’d do it like this:

IConfigurationRepository custRepository = ObjectFactory.GetInstance<IConfigurationRepository>();

So if a class needs an IConfigurationRepository then it can simply collect it from the abstraction-implementation map you declared at the entry point of the application.

Don’t to that. Never ever.

You objects will suddenly depend on this funny ObjectFactory static class instead:

public OnDemandAgentService()
{
	_loggingService = ObjectFactory.GetInstance<ILoggingService>();
	_cloudProvider = ObjectFactory.GetInstance<ICloudProvider>();
	_authService = ObjectFactory.GetInstance<IAuthorizationService>();
	_punisher = ObjectFactory.GetInstance<IUnauthorizedAccessPunishmentService>();
	_emailService = ObjectFactory.GetInstance<IEmailService>();
}

Even worse, the object factory may not have any concrete implementation in its internal map of abstractions. In which case your dependency will resolve to a null. You can make matters worse by adding a null check like this:

_loggingService = ObjectFactory.GetInstance<ILoggingService>();
if (_loggingService == null) _loggingService = new LoggingService();

Brilliant, you’ve just increased the degree of coupling to a new level. In addition these dependency declarations will be invisible to the caller. There’s no evidence in the public interface of OnDemandAgentService that it needs an ILoggingService and will do something with it. OnDemandAgentService in fact hides this information from the caller but that is clearly not what is meant by the OOP concept of information hiding.

We’re done with the core discussion of the SOLID concepts in this refresher series. We’ll clean up bits and pieces of our current classes and interfaces in the next post.

View the list of posts on Architecture and Patterns here.

Advertisements

About Andras Nemes
I'm a .NET/Java developer living and working in Stockholm, Sweden.

5 Responses to SOLID principles in .NET revisited part 7: the Dependency Inversion Principle

  1. Pingback: The Morning Brew - Chris Alcock » The Morning Brew #1861

  2. Pingback: Les liens de la semaine – Édition #132 | French Coding

  3. There goes the single responsibility principle. The fun fact is that the class still has the dependencies, more complicated perhaps, but just the same…

  4. Pingback: Architecture and patterns | Michael's Excerpts

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

ultimatemindsettoday

A great WordPress.com site

iReadable { }

.NET Tips & Tricks

Robin Sedlaczek's Blog

Developer on Microsoft Technologies

HarsH ReaLiTy

A Good Blog is Hard to Find

Softwarearchitektur in der Praxis

Wissenswertes zu Webentwicklung, Domain-Driven Design und Microservices

the software architecture

thoughts, ideas, diagrams,enterprise code, design pattern , solution designs

Technology Talks

on Microsoft technologies, Web, Android and others

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

Anything around ASP.NET MVC,WEB API, WCF, Entity Framework & AngularJS

Cyber Matters

Bite-size insight on Cyber Security for the not too technical.

Guru N Guns's

OneSolution To dOTnET.

Johnny Zraiby

Measuring programming progress by lines of code is like measuring aircraft building progress by weight.

%d bloggers like this: