Инъекция зависимостей — широко используемая концепция в большинстве приложений .NET. Одной из причин этого может быть то, что она поставляется прямо из коробки в .NET web API, но она также предоставляет некоторые другие важные преимущества.
Однако при запуске нового консольного приложения инъекция зависимостей недоступна из коробки, давайте добавим ее!
Установка 🧰
Установка довольно минимальна, и нам нужно только создать наше консольное приложение .NET:
~$ dotnet new console -o DepencyInjectionInConsoleApp
Наше простое консольное приложение 📦
Для нашего примера мы будем работать с текстовым редактором, который будет иметь проверку орфографии.
Давайте начнем с создания нашей программы проверки орфографии:
// ISpellChecker.cs
namespace DependencyInjectionInConsoleApp;
public interface ISpellChecker
{
bool IsValid(string text);
}
public class VeryBadSpellChecker : ISpellChecker
{
public bool IsValid(string text)
=> true;
}
Теперь мы можем создать наш текстовый редактор:
// TextEditor.cs
namespace DependencyInjectionInConsoleApp;
public class TextEditor
{
private readonly ISpellChecker _spellChecker;
public TextEditor(ISpellChecker spellChecker)
=> _spellChecker = spellChecker;
public void Edit(string text)
{
if (!_spellChecker.IsValid(text))
{
throw new InvalidOperationException("Text is not valid");
}
// Process the text
Console.WriteLine("Text processed");
}
}
Наконец, давайте создадим наш редактор и вызовем его:
// Program.cs
using DependencyInjectionInConsoleApp;
var spellChecker = new VeryBadSpellChecker();
var textEditor = new TextEditor(spellChecker);
textEditor.Edit("Hello, world!");
По желанию, мы можем запустить программу для проверки нашей настройки:
~/DepencyInjectionInConsoleApp$ dotnet run
Text processed
Все готово!
Проблема
В нашем примере у нас всего несколько классов и одна зависимость TextEditor
от ISpellChecker
, но в реальном проекте у нас, вероятно, будут десятки классов.
В этом случае создание наших объектов в файле Program.cs
не будет хорошим решением, так как это приведет к появлению многих строк кода, чисто посвященных созданию объектов, что-то вроде:
var logger = new SpecificLogger();
var databaseContext = new DatabaseContext(logger);
var spellChecker = new FrenchSpellChecker(logger);
var currentUserService = new CurrentUserService(logger, databaseContext)
var textEditor = new TextEditor(currentUserService, spellChecker);
// and so on
Более того, если мы когда-нибудь изменим зависимость объекта, нам, возможно, придется пройти весь процесс инициализации. Хуже того, если мы имеем дело с различными реализациями одного и того же интерфейса, нам лучше быть осторожными:
var spellChecker1 = new FrenchSpellChecker();
var spellChecker2 = new EnglishSpellChecker();
// ...
var englandAmbassyTextEditor = new TextEditor(spellChecker1);
// ^ We probably not meant it to be the french one !
Перепутать зависимости здесь было бы трудно, поскольку они были бы погребены под многими другими строками кода.
Это некоторые проблемы, которые инъекция зависимостей могла бы исправить, и нам лучше это сделать!
Привнесение инъекции зависимостей 💉
Когда я впервые столкнулся с задачей внедрения инъекции зависимостей в консольное приложение, я ожидал, что это будет тяжелый процесс, но, к моему удивлению, это действительно не так.
Во-первых, нам нужно добавить пакет расширений Microsoft для внедрения зависимостей:
~DepencyInjectionInConsoleApp$ dotnet add package Microsoft.Extensions.DependencyInjection
Теперь мы можем настроить наш контейнер инъекции зависимостей:
using Microsoft.Extensions.DependencyInjection;
var serviceProvider = new ServiceCollection()
.AddTransient<ISpellChecker, VeryBadSpellChecker>()
.AddTransient<TextEditor>()
.BuildServiceProvider();
И наконец, избавимся от ручной инициализации и получим наш TextEditor
из нашего контейнера инъекции зависимостей:
- var spellChecker = new VeryBadSpellChecker();
- var textEditor = new TextEditor(spellChecker);
+ var textEditor = serviceProvider.GetRequiredService<TextEditor>();
textEditor.Edit("Hello, world!");
Теперь мы можем запустить нашу программу еще раз, чтобы убедиться, что ничего не изменилось:
~/DepencyInjectionInConsoleApp$ dotnet run
Text processed
И мы закончили!
Извлечение уроков
Начиная с нашего простого консольного приложения, мы успешно использовали возможности инъекции зависимостей в нашем проекте, сделав его более простым при вызове объектов и упростив обновление их зависимостей.
В этом примере мы использовали контейнер, предоставленный Microsoft, но существует и несколько других, выбирайте их в соответствии с вашими потребностями и ощущениями!