为什么我必须 "wire up" 在 ASP.NET MVC 中进行依赖注入?
Why do I have to "wire up" Dependency Injection in ASP.NET MVC?
我认为接口、多态性和通过依赖注入实现控制反转等模式的全部原因是为了避免紧耦合、分离、模块化等。
为什么我必须像 ASP.NET 那样显式 "wire up" 一个具体 class 的接口?难道我让注册表成为某种 类型的耦合吗?喜欢,
services.AddTransient<ILogger, DatabaseLogger>();
如果我使用一个记录器 ILogger,并创建一个实现该接口的文件和数据库 class,会怎样。
在我的 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();
}
}
}
那么为什么我必须注册或 "wire up" 什么?这不是通过构造函数进行依赖注入吗(我是不是有根本性的误解)?我可以将任何实现 ILogger 的记录器放在那里。
我会创建其中两个吗?
services.AddTransient<ILogger, DatabaseLogger>();
services.AddTransient<ILogger, FileLogger>();
你实际上会进一步抽象它。在您的代码中,您将创建一个工厂。
Define an interface for creating an object, but let subclasses decide
which class to instantiate. Factory Method lets a class defer
instantiation to subclasses.
所以你本质上会有以下内容:
// 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();
}
}
使用当前的定义方式,当您将两个依赖项连接到您的容器时,您将收到以下 IEnumerable<ILogger>
。但是,为了正确包装它,我们将执行以下操作:
public interface ILoggerFactory
{
ILogger CreateDbLogger();
ILogger CreateLogger();
}
public class LoggerFactory : ILoggerFactory
{
public ILogger CreateDbLogger() => new DatabaseLogger();
public ILogger CreateLogger() => new TextLogger();
}
所以当我们在我们的依赖注入中注册我们的 Factory
时,我们会简单地写 services.AddTransient<ILoggerFactory, LoggerFactory>();
。当你注入工厂时,你可以简单地使用:
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(...);
}
现在,这可能会导致过度曝光。但希望这能澄清经常使用的依赖注入的用法。您可以通过输入 "Factory Pattern" 或 "Factory Method".
来阅读更多内容
您的示例也是依赖注入,但是,这是一个非常简单的场景。虽然您可以手动完成,但在任何规模较大的现实世界场景中它都会失控。
假设您的 DemoClass
有 10 个依赖项,并且每个依赖项都有自己的一些依赖项,这在实际应用程序中很容易出现。现在想象一下手动实例化所有这些以创建更高级别的服务:
DemoClass myDemo = new DemoClass(new DatabaseLogger(new DbLoggerDependency1(), new DbLoggerDependency2(), new DbLoggerDependency3(), new DbLoggerDependency4()), new OtherDemoClassDependency(new OtherDependencyDependency1(), new OtherDependencyDependency2()));
而且你必须在任何需要 DemoClass
的地方这样做。变丑得很快吧?这只是一些依赖项。
现在想象一下,在开发代码库时使用测试工具,您必须使用模拟版本重新做一遍,而使用布线,您只需为测试工具引入一个新配置。
这种连接让您可以在配置文件中保持所有注入干净、独立和可维护,并让您轻松地从单个控制点交换实现:
services.AddTransient<ILogger, DatabaseLogger>();
services.AddTransient<IDbLoggerDependency1, DbLoggerDependency1>();
services.AddTransient<IDbLoggerDependency2, DbLoggerDependency2>();
services.AddTransient<IDbLoggerDependency3, DbLoggerDependency3>();
services.AddTransient<IDbLoggerDependency4, DbLoggerDependency4>();
services.AddTransient<IOtherDemoClassDependency, OtherDemoClassDependency>();
services.AddTransient<IOtherDependencyDependency1, OtherDependencyDependency1>();
services.AddTransient<IOtherDependencyDependency2, OtherDependencyDependency2>();
services.AddTransient<IDemoClass, DemoClass>();
然后在任何需要 DemoClass
的地方,您都可以让 IoC 根据您的配置负责实例化和注入正确的依赖项,这样您就不需要编写所有样板代码:
class Foo
{
private readonly IDemoClass _myDemo;
public Foo(IDemoClass myDemo)
{
// myDemo is instantiated with the required dependencies based on your config
_myDemo = myDemo;
}
public SomeMethod()
{
_myDemo.logThis("this is a message");
}
}
某些容器(例如 AutoFac)甚至支持 属性 注入,因此您甚至不需要包含构造函数。
我认为接口、多态性和通过依赖注入实现控制反转等模式的全部原因是为了避免紧耦合、分离、模块化等。
为什么我必须像 ASP.NET 那样显式 "wire up" 一个具体 class 的接口?难道我让注册表成为某种 类型的耦合吗?喜欢,
services.AddTransient<ILogger, DatabaseLogger>();
如果我使用一个记录器 ILogger,并创建一个实现该接口的文件和数据库 class,会怎样。
在我的 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();
}
}
}
那么为什么我必须注册或 "wire up" 什么?这不是通过构造函数进行依赖注入吗(我是不是有根本性的误解)?我可以将任何实现 ILogger 的记录器放在那里。
我会创建其中两个吗?
services.AddTransient<ILogger, DatabaseLogger>();
services.AddTransient<ILogger, FileLogger>();
你实际上会进一步抽象它。在您的代码中,您将创建一个工厂。
Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.
所以你本质上会有以下内容:
// 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();
}
}
使用当前的定义方式,当您将两个依赖项连接到您的容器时,您将收到以下 IEnumerable<ILogger>
。但是,为了正确包装它,我们将执行以下操作:
public interface ILoggerFactory
{
ILogger CreateDbLogger();
ILogger CreateLogger();
}
public class LoggerFactory : ILoggerFactory
{
public ILogger CreateDbLogger() => new DatabaseLogger();
public ILogger CreateLogger() => new TextLogger();
}
所以当我们在我们的依赖注入中注册我们的 Factory
时,我们会简单地写 services.AddTransient<ILoggerFactory, LoggerFactory>();
。当你注入工厂时,你可以简单地使用:
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(...);
}
现在,这可能会导致过度曝光。但希望这能澄清经常使用的依赖注入的用法。您可以通过输入 "Factory Pattern" 或 "Factory Method".
来阅读更多内容您的示例也是依赖注入,但是,这是一个非常简单的场景。虽然您可以手动完成,但在任何规模较大的现实世界场景中它都会失控。
假设您的 DemoClass
有 10 个依赖项,并且每个依赖项都有自己的一些依赖项,这在实际应用程序中很容易出现。现在想象一下手动实例化所有这些以创建更高级别的服务:
DemoClass myDemo = new DemoClass(new DatabaseLogger(new DbLoggerDependency1(), new DbLoggerDependency2(), new DbLoggerDependency3(), new DbLoggerDependency4()), new OtherDemoClassDependency(new OtherDependencyDependency1(), new OtherDependencyDependency2()));
而且你必须在任何需要 DemoClass
的地方这样做。变丑得很快吧?这只是一些依赖项。
现在想象一下,在开发代码库时使用测试工具,您必须使用模拟版本重新做一遍,而使用布线,您只需为测试工具引入一个新配置。
这种连接让您可以在配置文件中保持所有注入干净、独立和可维护,并让您轻松地从单个控制点交换实现:
services.AddTransient<ILogger, DatabaseLogger>();
services.AddTransient<IDbLoggerDependency1, DbLoggerDependency1>();
services.AddTransient<IDbLoggerDependency2, DbLoggerDependency2>();
services.AddTransient<IDbLoggerDependency3, DbLoggerDependency3>();
services.AddTransient<IDbLoggerDependency4, DbLoggerDependency4>();
services.AddTransient<IOtherDemoClassDependency, OtherDemoClassDependency>();
services.AddTransient<IOtherDependencyDependency1, OtherDependencyDependency1>();
services.AddTransient<IOtherDependencyDependency2, OtherDependencyDependency2>();
services.AddTransient<IDemoClass, DemoClass>();
然后在任何需要 DemoClass
的地方,您都可以让 IoC 根据您的配置负责实例化和注入正确的依赖项,这样您就不需要编写所有样板代码:
class Foo
{
private readonly IDemoClass _myDemo;
public Foo(IDemoClass myDemo)
{
// myDemo is instantiated with the required dependencies based on your config
_myDemo = myDemo;
}
public SomeMethod()
{
_myDemo.logThis("this is a message");
}
}
某些容器(例如 AutoFac)甚至支持 属性 注入,因此您甚至不需要包含构造函数。