4

Я думал, что вся причина для интерфейсов, полиморфизма и шаблона, такого как инверсия управления через инъекции зависимостей, заключалась в том, чтобы избежать жесткой связи, разделения, модульности и т. Д.Почему я должен «подключать» инъекцию зависимостей в ASP.NET MVC?

Почему тогда я должен явно «подключить» интерфейс к конкретному классу, как в ASP.NET? Не будет ли у меня реестра быть некоторые вид связи? Мол,

services.AddTransient<ILogger, DatabaseLogger>(); 

Что делать, если взять регистратор, ILogger, и создать файл базы данных и класс, которые реализуют этот интерфейс.

В моей 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(); 

     } 
    } 
} 

Так почему же я должен зарегистрировать или "провод на" что-нибудь? Разве это не зависимость от зависимостей через конструктор (у меня есть фундаментальное недоразумение)? Я мог бы разместить там любого регистратора, который реализовал ILogger.

Могу ли я создать два из них?

services.AddTransient<ILogger, DatabaseLogger>(); 
services.AddTransient<ILogger, FileLogger>(); 
+0

Хорошо, если только интерфейс имеет только одну реализацию, как предполагается, что программа должна знать, какой. Вам нужно будет указать его на один из них или вы можете получить неожиданное поведение. –

+0

@AlexKrupka Я не уверен, что понимаю. У меня две реализации. Я только экземпляр одного из них. – johnny

+1

Для того чтобы DI был действительно ценным (по моему опыту), вам нужно посмотреть на более сложные сценарии. Например, компания A и компания B используют ваш сайт, но компания A является канадкой, а компания B - американец. Таким образом, вы привязываете реализацию расчета налогов на основе того, к какой компании принадлежит пользователь, а не к имплементации. который обрабатывает оба оператора If: –

ответ

2

Ваш пример - Инъекция зависимостей, однако, это очень простой сценарий. Хотя вы можете сделать это вручную, он выходит из-под контроля на любом значимом сценарии реального мира.

Представьте, что ваш 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 даже поддерживать имущество поэтому вам даже не потребуется включать конструктор.

+0

Saeb, спасибо. Как он знает, что такое зависимость от чего? В этом примере говорится: «services.AddTransient ();" Но как система знает, что DemoClass нуждается во всех этих зависимостях? Использует ли он отражение для просмотра конструктора или сеттера? – johnny

+1

@johnny Если часто используется отражение. – Greg

+1

yw @johnny, да, для каждого класса, который требуется создать, он смотрит на конструктор, чтобы узнать зависимости, и он создает экземпляры этих зависимостей на основе ваших конфигураций. –

3

Вы бы на самом деле отвлекли его немного дальше. В вашем коде вы должны создать фабрику.

Определите интерфейс для создания объекта, но пусть подклассы определяют , какой класс необходимо создать. Factory Method позволяет классу отложить экземпляр для подклассов.

Example Factory Method

Таким образом, вы бы в сущности есть следующие:

// 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(...); 
} 

Теперь это может привести к экспозиции. Но, надеюсь, это объясняет использование с Injection Dependency, которое используется часто. Вы можете прочитать немного больше, набрав «Factory Pattern» или «Factory Method».

+0

Я хотел выбрать этот, но я не видел, как это помогло мне с вопросом. – johnny

+0

@johnny Это демонстрирует способ обернуть на фабрике, чтобы избежать чрезмерной связи с одним и тем же интерфейсом. Это создает немного больше свободы, чем прямая инъекция, позволяющая сказать репозиторий данных для прямого контекста данных. Вот где я думал, что основная часть вашего гнева была, что если у вас есть один интерфейс, используемый, например, для нескольких репозиториев, то вам нужно определить каждый из них, и он будет возвращать тот же самый в формате IEnumerable. Кажется, что вы поняли DI достаточно, поэтому нет причин избивать мертвую лошадь, почему она мощна, особенно в более крупных условиях. – Greg

Смежные вопросы