I thought the whole reason for Interfaces, Polymorphism, and a pattern like Inversion of Control via Dependency Injection was to avoid tight coupling, separation, modularity, and so on.
Why then do I have to explicitly "wire up" an Interface to a concrete class like in ASP.NET? Won't me having the registry be some sort of coupling? Like,
services.AddTransient<ILogger, DatabaseLogger>();
What if I take a logger, ILogger, and create a file and database class that implement that interface.
In my IoC,
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
public interface ILogger
{
void logThis(string message);
}
public class DatabaseLogger : ILogger
{
public void logThis(string message)
{
//INSERT INTO table.....
System.Console.WriteLine("inserted into a databse table");
}
}
public class FileLogger : ILogger
{
public void logThis(string message)
{
System.Console.WriteLine("logged via file");
}
}
public class DemoClass
{
public ILogger myLogger;
public DemoClass(ILogger myLogger)
{
this.myLogger = myLogger;
}
public void logThis(string message)
{
this.myLogger.logThis(message);
}
}
class Program
{
static void Main(string[] args)
{
DemoClass myDemo = new DemoClass(new DatabaseLogger()); //Isn't this Dependency Injection?
myDemo.logThis("this is a message");
Console.ReadLine();
}
}
}
So why do I have to register or "wire up" anything? Isn't this Dependency Injection via the Constructor (Do I have fundamental misunderstanding)? I could put any logger in there that implemented ILogger.
Would I create two of these?
services.AddTransient<ILogger, DatabaseLogger>();
services.AddTransient<ILogger, FileLogger>();
Your example is Dependency Injection too, however, it's a very simple scenario. While you could do it manually, it gets out of hand on any sizable real-world scenario.
Imagine your
DemoClass
had 10 dependencies, and each of those dependencies had a few dependencies of their own, which can easily be the case in a real-world application. Now imagine instantiating all those manually to create your higher level service:And you have to do that everywhere you need your
DemoClass
. Gets ugly pretty fast right? and that's just a few dependencies.Now imagine working on a test harness while developing your code-base, you have to do it all over again with mock versions whereas with wiring you can just introduce a new config for your test harness.
This wiring up lets you keep all the injection clean, separate and maintainable in a config file and lets you swap out implementations from a single point of control easily:
And then anywhere you need your
DemoClass
you can just let your IoC take care of instantiating and injecting the correct dependencies based on your config so you won't need to write all the boilerplate code:Some containers like AutoFac even support property injection so you won't even need to include the constructor.