SOLID principles in .NET revisited part 6: the Interface Segregation Principle

Introduction

In the previous post we continued to discuss the Liskov Substitution Principle. We saw a subtle example of breaking LSP by trying to force an object to implement an interface even though not all methods of the interface made sense for it. The client object, i.e. OnDemandAgentService cannot consume all implementations of IAuthorizationService without fully being able to carry out its tasks.

A possible solution comes in the form of letter ‘I’ in SOLID, which stands for the Interface Segregation Principle (ISP). ISP states that clients should not be forced to depend on interfaces and methods they do not use. Applying ISP correctly will result in a lot of small interfaces instead of handful of large ones with lots of methods. The more methods to an interface has the more likely it is that an implementation will not be able to fulfil all parts of the contract. The IAuthorizationService only has 2 methods and we immediately found an example where a class, the AuthorizationService only could implement one of them.

Applying ISP to the problem

Not all is lost with the AuthorizationService class. It can at least implement one of the methods of IAuthorizationService. We could remove the LockOut method from IAuthorizationService and then we’ll be fine. However, that will be problematic for DatabaseAuthorizationService and OnDemandAgentService. We can resolve the problem by breaking up IAuthorizationService into two interfaces. We’ll keep IAuthorizationService with only one method and put the LockOut method into another interface:

public interface IAuthorizationService
{
	bool IsAuthorized(string username, string password);
}

public interface IUnauthorizedAccessPunishmentService
{
	bool LockOut(string username);
}

AuthorizationService can safely implement IAuthorizationService:

public class AuthorizationService : IAuthorizationService
{
	public bool IsAuthorized(string username, string password)
	{
		return (username == "admin" && password == "passw0rd");
	}
}

DatabaseAuthorizationService can implement both:

public class DatabaseAuthorizationService : IAuthorizationService, IUnauthorizedAccessPunishmentService
{
	private List<string> users;

	public DatabaseAuthorizationService()
	{
		users = new List<string>();
	}

	public bool IsAuthorized(string username, string password)
	{
		return true;
	}

	public bool LockOut(string username)
	{
		return users.Remove(username);
	}
}

Then we’ll need to add the usual private field and public getter/setter to OnDemandAgentService:

private IUnauthorizedAccessPunishmentService _punisher;

public IUnauthorizedAccessPunishmentService Punisher
{
	get
	{
		if (_punisher == null)
		{
			_punisher = new DatabaseAuthorizationService();
		}
		return _punisher;
	}
	set
	{
		if (value != null)
		{
			_punisher = value;
		}
	}
}

The StartNewOnDemandMachine method can now call the appropriate IUnauthorizedAccessPunishmentService in StartNewOnDemandMachine():

public OnDemandAgent StartNewOnDemandMachine()
{
	EmailService emailService = new EmailService();
	LoggingService.LogInfo("Starting on-demand agent startup logic");
	try
	{
		if (AuthorizationService.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;
	}
}

ISP violation in .NET

.NET has several large interfaces where it’s often difficult to implement all methods. One good example is the IConvertible interface which you can implement in your own classes so that they can converted into an integer, a double, a date etc. This interface comes with 17 methods like the following:

public bool ToBoolean(IFormatProvider provider)
{
	
}

public DateTime ToDateTime(IFormatProvider provider)
{
	
}

public double ToDouble(IFormatProvider provider)
{
	
}

I cannot really think of any object that can be converted to all other objects provided by the IConvertible interface. It is possible to provide some fake and bogus return values, like “return true” for the ToBoolean method but would it be truly meaningful for that object? Maybe, maybe not. However, it’s hard to think of an object that meaningfully implement all 17 members of the IConvertible interface.

So calm down, not even .NET is SOLID-proof.

In the next post we’ll look at the Dependency Inversion Principle.

View the list of posts on Architecture and Patterns here.

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

4 Responses to SOLID principles in .NET revisited part 6: the Interface Segregation Principle

  1. Ilkin says:

    Thanks.Does creating code very solid makes slow program?For example SRP so much classes,so much interfaces.

    • Andras Nemes says:

      Hello,
      No, don’t worry about that. If your code is slow then it’s usually due to below-optimal queries, HTTP calls, file I/O and not to a large number of small classes and interfaces.
      //Andras

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

Leave a comment

Elliot Balynn's Blog

A directory of wonderful thoughts

Software Engineering

Web development

Disparate Opinions

Various tidbits

chsakell's Blog

WEB APPLICATION DEVELOPMENT TUTORIALS WITH OPEN-SOURCE PROJECTS

Once Upon a Camayoc

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