Как мне создать несколько экземпляров одного класса с. net core DI? - PullRequest
0 голосов
/ 10 января 2020

Я новичок в внедрении Dependency Injection, поэтому, пытаясь реализовать его в моих проектах net core 3.0, я столкнулся со сценарием, в котором мне нужно несколько экземпляров одной службы, зарегистрированной в моем поставщике услуг. В качестве обходного пути я прибег к внедрению самого IServiceProvider и простому вызову GetRequiredService<T> (служб с временной областью) несколько раз. Это работает, но, похоже, это анти-паттерн, и я не уверен, как я должен это делать правильно.

Часть проблемы при исследовании этого вопроса заключается в настойчивости в каждом ответе, что Потребность в нескольких экземплярах одного класса сама по себе является запахом кода, но в качестве примера, скажем, я даю инструкции духовке для автоматизации процесса выпечки. Пользователь определит температуру и время для каждого рецепта в представлении, затем представление вернет List<string>, который представляет этот список шагов в контроллер. Примерный список шагов может выглядеть так:

//Bake for 500 degrees Fahrenheit for 30 minutes, then 225F for another 90
["500F,30","225F,90"]

Затем мне нужно будет преобразовать его в List<CookingStep> в контроллере, чтобы добавить к моему Recipe объекту. Я делал что-то вроде этого:

IOven Oven;
IRecipe newRecipe;
IServiceProvider sp;

//public OvenController... (constructor with dependencies injected here)

public async Task<IActionResult> BakeSomething(List<string> steps)
{

  foreach(string s in steps)
  {
    IRecipeStep recipeStep = sp.GetRequiredService<IRecipeStep>();
    recipeStep.ParseTimeAndTemperatureFromString(s);
    newRecipe.Steps.Add(recipeStep);
  }

  await Oven.Bake(newRecipe);
  return View("RecipeComplete",newRecipe);
}

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

Таким образом, я могу создать фабрику, даже обобщенную c, но это (по общему признанию, чрезмерно simpisti c пример) действительно лучше, чем приведенный выше шаблон поиска служб?

IOven Oven;
IRecipe newRecipe;
IRecipeStepFactory rsFactory;

//public OvenController... (constructor with dependencies injected here)

public async Task<IActionResult> BakeSomething(List<string> steps)
{

  foreach(string s in steps)
  {
    IRecipeStep recipeStep = rsFactory.NewStepFromString(s);
    newRecipe.Steps.Add(recipeStep);
  }

  await Oven.Bake(newRecipe);
  return View("RecipeComplete",newRecipe);
}

public class RecipeStepFactory : IRecipeStepFactory
{
  public IRecipeStep NewStepFromString(string s)
  {
    RecipeStep recipeStep = new RecipeStep();
    recipeStep.ParseTimeAndTemperatureFromString(s);
    return recipeStep;
  }
}

(заранее прошу прощения за любые проблемы с изложением или содержанием моего вопроса, это первый раз, когда мне пришлось задать свой вопрос)

Ответы [ 2 ]

2 голосов
/ 10 января 2020

Да, фабрика лучше. Другой подход - определить делегата следующим образом:

public delegate IRecipeStep RecipeStepFactory(string steps);

и затем зарегистрировать его следующим образом (Func<string, RecipeService> также будет работать, но делегат будет чище):

services.AddTransient<RecipeStepFactory>(sp =>
{
    // resolve any services you need from the resolved IServiceProvider below
    // since you don't need any in this case, I've commented it out
    // IServiceProvider provider = sp.GetRequiredService<IServiceProvider>();
    return s =>
    {
        RecipeStep step = new RecipeStep();
        step.ParseTimeAndTemperatureFromString(s);
        return step;
    };
});

Тогда вы могу добавить:

private readonly RecipeStepFactory _recipeStepFactory;

public OvenController(RecipeStepFactory recipeStepFactory)
{
    _recipeStepFactory = recipeStepFactory;
}

public async Task<IActionResult> BakeSomething(List<string> steps)
{

  foreach(string s in steps)
  {
    IRecipeStep recipeStep = _recipeStepFactory(s);
    newRecipe.Steps.Add(recipeStep);
  }

  await Oven.Bake(newRecipe);
  return View("RecipeComplete",newRecipe);
}

Мне нравится этот подход, потому что он сохраняет все в моей композиции root, но в зависимости от сложности фабрики, ваш текущий подход может быть лучше. Лично я использую оба подхода.

Я фактически использую для этого Autofa c (другой контейнер DI), потому что я могу просто определить делегата в сервисе, который я объявляю, и Autofa c автоматически подключает его (см. делегатские фабрики ), но очевидно, что Autofa c, вероятно, больше, чем требуется для небольших проектов, поэтому, если вам это не нужно, вы можете использовать приведенный выше шаблон.

1 голос
/ 10 января 2020

ИМХО, и с моим опытом, я бы склонялся к фабричному шаблону (основываясь на том, что я понял, и только на вашем примере stmt / example).

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

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

Один такой, который я бы любил, доступен на youtube . и другой (если у вас есть учетная запись Pluralsight) на здесь .

Это только мои личные предложения. Я надеюсь, что, основываясь на этом обучении и знаниях, это может варьироваться в зависимости от реальных проблем и решений.

...