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>();
You would actually abstract it a bit further. In your code, you would create a factory.
Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.
So you would in essence have the following:
// Interface:
public interface ILogger : IDisposable
{
void Log<TEntity>(TEntity entity);
}
// Concrete Implementation:
public class DatabaseLogger : ILogger
{
public void Log<TEntity>(TEntity entity)
{
throw new NotImplementedException();
}
}
public class TextLogger : ILogger
{
public void Log<TEntity>(TEntity entity)
{
throw new NotImplementedException();
}
}
With the current way this is defined, you would receive the following IEnumerable<ILogger>
when you wired both dependencies to your container. But, to correctly wrap this we would do the following:
public interface ILoggerFactory
{
ILogger CreateDbLogger();
ILogger CreateLogger();
}
public class LoggerFactory : ILoggerFactory
{
public ILogger CreateDbLogger() => new DatabaseLogger();
public ILogger CreateLogger() => new TextLogger();
}
So when we register our Factory
within our dependency injection, we would simply write services.AddTransient<ILoggerFactory, LoggerFactory>();
. When you inject the factory, you would simply be able to use:
public class Example
{
public ILoggerFactory Factory { get; }
public Example(ILoggerFactory factory)
{
Factory = factory;
}
// Utilize in a method.
using(var logger = Factory.CreateDbLogger())
logger.Log(...);
// Utilize in a method.
using(var logger = Factory.CreateLogger())
logger.Log(...);
}
Now, this can introduce over exposure. But hopefully this clarifies a usage with Dependency Injection which is utilized frequently. You can read a bit more on it by typing "Factory Pattern" or "Factory Method".
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With