I've been guilty of having a 1-to-1 relationship between my interfaces and concrete classes when using dependency injection. When I need to add a method to an interface, I end up breaking all the classes that implement the interface.
This is a simple example, but let's assume that I need to inject an ILogger
into one of my classes.
public interface ILogger
{
void Info(string message);
}
public class Logger : ILogger
{
public void Info(string message) { }
}
Having a 1-to-1 relationship like this feels like a code smell. Since I only have a single implementation, are there any potentially issues if I create a class and mark the Info
method as virtual to override in my tests instead of having to create an interface just for a single class?
public class Logger
{
public virtual void Info(string message)
{
// Log to file
}
}
If I needed another implementation, I can override the Info
method:
public class SqlLogger : Logger
{
public override void Info(string message)
{
// Log to SQL
}
}
If each of these classes have specific properties or methods that would create a leaky abstraction, I could extract out a base class:
public class Logger
{
public virtual void Info(string message)
{
throw new NotImplementedException();
}
}
public class SqlLogger : Logger
{
public override void Info(string message) { }
}
public class FileLogger : Logger
{
public override void Info(string message) { }
}
The reason why I didn't mark the base class as abstract is because if I ever wanted to add another method, I wouldn't break existing implementations. For example, if my FileLogger
needed a Debug
method, I can update the base class Logger
without breaking the existing SqlLogger
.
public class Logger
{
public virtual void Info(string message)
{
throw new NotImplementedException();
}
public virtual void Debug(string message)
{
throw new NotImplementedException();
}
}
public class SqlLogger : Logger
{
public override void Info(string message) { }
}
public class FileLogger : Logger
{
public override void Info(string message) { }
public override void Debug(string message) { }
}
Again, this is a simple example, but when I should I prefer an interface?