Как можно создать и настроить несколько экземпляров одного и того же класса с внедрением зависимостей?
Я видел похожие вопросы/учебники, но пока не понимаю.
Вот пример:
Контейнер
public class App()
{
public IHost Host { get; }
public App()
{
Host = Microsoft.Extensions.Hosting.Host.
CreateDefaultBuilder().
ConfigureServices((context, services) =>
{
services.AddSingleton<ITemperatureSensor, AcmeTemperatureSensor>();
services.AddSingleton<IAcmeMachine, AcmeMachine>();
services.Configure<TemperatureSensorOptions>(context.Configuration.GetSection(nameof(TemperatureSensorOptions)));
}).
Build();
}
}
Акме машина
Именно сюда должны быть вставлены датчики температуры.
public class AcmeMachine()
{
public AcmeMachine( Something? )
{
// How inject the temperature sensors?
}
ITemperatureSensor WaterSensor
ITemperatureSensor AirSensor
}
Датчики температуры
public interface ITemperatureSensor
{
string SerialNumber;
double GetTemperature();
}
public class AcmeTemperatureSensor()
{
public string SerialNumber { get; }
public AcmeTemperatureSensor(IOptions<TemperatureSensorOptions> options)
{
SerialNumber = options.Value.SerialNumber;
}
public double GetTemperature()
{
return 25.0;
}
}
Настройки
appsettings.json
{
"WaterSensor": {
"TemperatureSensorOptions": {
"SerialNumber": "123",
},
},
"AirSensor": {
"TemperatureSensorOptions": {
"SerialNumber": "456",
},
}
}
Я бы предложил изменить настройки приложения на что-то вроде:
{
// ...
"Sensors": {
"WaterSensor": {
"TemperatureSensorOptions": {
"SerialNumber": "123"
}
},
"AirSensor": {
"TemperatureSensorOptions": {
"SerialNumber": "456"
}
}
}
}
Тогда вы сможете прочитать конфиг как:
builder.Services.Configure<Dictionary<string, Sensor>>(builder.Configuration.GetSection("Sensors"));
class Sensor
{
public TemperatureSensorOptions TemperatureSensorOptions { get; set; }
}
class TemperatureSensorOptions
{
public string SerialNumber { get; set; }
}
И внедрить его в какой-нибудь сервис как IOptions<Dictionary<string, Sensor>>
.
Или используйте специальный класс вместо Dictionary
:
builder.Services.Configure<Sensors>(builder.Configuration.GetSection("Sensors"));
class Sensors
{
public Sensor WaterSensor { get; set; }
public Sensor AirSensor { get; set; }
}
Обратите внимание, что если датчик не имеет других настроек, TemperatureSensorOptions
можно сгладить/удалить, чтобы упростить настройку.
Если вам нужно построить AcmeTemperatureSensor
, внедрив туда эти настройки, вам нужно будет либо создать их вручную, либо следовать заводскому шаблону.
Из комментариев:
Если кто-то использует ручное построение или заводской шаблон, есть ли хороший способ получить доступ к службам контейнеров IoC?
Лично я бы выбрал фабричный шаблон, который можно довольно легко реализовать с помощью Func<>
(хотя у него могут быть и некоторые недостатки, например, добавление заводских параметров станет не таким простым):
enpointServices.AddTransient<Func<TemperatureSensorOptions, AcmeTemperatureSensor>>(sp =>
opts => new AcmeTemperatureSensor(opts, sp.GetRequiredService<ILogger<AcmeTemperatureSensor>>()));
А затем введите Func<TemperatureSensorOptions, AcmeTemperatureSensor>
как зависимость и вызовите ее.
Также обычно в таких случаях я создаю и регистрирую класс DI, например AcmeTemperatureSensorDeps
, чтобы инкапсулировать все AcmeTemperatureSensor
зависимости от DI, чтобы упростить управление ими.
Если кто-то использует ручное построение или заводской шаблон, есть ли хороший способ получить доступ к службам контейнеров IoC? Посоветуете что-нибудь подобное _logger = Host.Services.GetRequiredService<ILogger<Foo>>();
?
@JasonC Немного обновил ответ, посмотрите.
Спасибо за обновление вашего ответа. Вы упомянули добавление класса, который инкапсулирует все необходимые зависимости, что упрощает управление ими. Что вы имеете в виду под проще управлять? Просто класс, который его использует, имеет только один аргумент в конструкторе?
@JasonC «Просто класс, который его использует, имеет только один аргумент в конструкторе?» - короче - да, у вашего класса есть один параметр кроме "фабричных".
Вы можете изменить конструкторы классов на следующие:
public AcmeMachine(ITemperatureSensor waterSensor, ITemperatureSensor airSensor)
public AcmeTemperatureSensor(TemperatureSensorOptions options)
С этими подписями вы можете иметь следующую конфигурацию DI:
var temp = Configuration.GetSection("WaterSensor").Get<TemperatureSensorOptions>();
var air = Configuration.GetSection("AirSensor").Get<TemperatureSensorOptions>();
services.AddSingleton(sp => new AcmeMachine(
waterSensor: new AcmeTemperatureSensor(temp),
airSensor: new AcmeTemperatureSensor(air));
Было бы это хорошим решением, если бы было N датчиков? Скажем, N = 5, 10 или 100?
В этом случае, пожалуйста, обновите свой вопрос и покажите, как вы собираетесь использовать такой список из 100 датчиков и как вы хотите различать их. Пропингуйте меня после того, как вы обновите свой вопрос, и я обновлю свой ответ.
Это тоже очень похожий вопрос