Как вызвать конструктор класса, который создается с помощью Structure Map в C#
У меня есть интерфейс под названием ILogger, который в основном содержит некоторые методы для регистрации.
Ilogger.cs
public interface ILogger
{
void LogError(string message, Exception exception = null);
void LogMessage(string message);
void LogValidationError(UploadResult uploadResult);
void LogValidationError(ValidationResult validationResult);
void LogProcessingError(string processingError);
}
У меня есть класс LogHelper, который реализует этот интерфейс. Класс LogHelper создается через StructureMap, как
ObjectFactory.Initialize(
request =>
{
request.For<ILogger>().Singleton().Use<LogHelper>();
});
У меня есть много классов, в конструкторе которых я просто создаю экземпляр этого класса и вызываю методы для регистрации информации. Например: у меня есть класс скажем Dummy1, в конструкторе которого я создаю экземпляр LogHelper как:
public Dummy1()
{
this.logger = ObjectFactory.GetInstance<ILogger>();
}
В LogHelper у меня есть метод, который в основном создает файл журнала и записывает сообщение, переданное в качестве параметра к нему.
public void LogMessage(string message)
{
using (var writer = this.GetTextWriter(this.messageFilename))
{
writer.WriteLine(message);
}
}
В настоящее время имя файла жестко закодировано в постоянное свойство класса LogHelper как private string messageFilename = "logs\\UserCreationResult.log"
;
Но я хочу, чтобы имя файла отправлялось динамически всякий раз, когда создается экземпляр LogHelper. Я думал о наличии свойства класса и определяю это свойство в конструкторе всякий раз, когда создается экземпляр класса. Но поскольку класс LogHelper создается как ObjectFactory.GetInstance<ILogger>()
, Я не могу вызвать конструктор, в котором я могу передать имя файла.
2 ответа
К сожалению, то, как вы это делаете, немного самоубийственно. Ваши занятия знают только о ILogger
, а не какая-то конкретная реализация ILogger
, Это хорошо - это означает, что реализация может записывать в файл, таблицу SQL или что угодно.
Но если ваш класс знает только о ILogger
, а не реализация, тогда как ваш класс узнает, что регистратору нужен путь к файлу? Если вы измените свой метод подписи в ILogger
чтобы содержать путь к файлу, то происходят две вещи.
- Становится невозможным иметь какую-либо реализацию
ILogger
который не записывает в файл (если он не игнорирует путь к файлу, что было бы очень странно) - Теперь тот класс, который вызывает регистратор, должен знать путь к файлу. Откуда этот класс получит путь к файлу? Будет ли оно храниться в классе? В этом случае вы получите класс, который не работает, если только он не является частью сборки, выполняющейся на компьютере, где он может записать в этот точный путь к файлу.
Вместо этого детали того, где и как войти, должны жить где-то в вашем ILogger
реализация. Это ближе к принципу единой ответственности. Класс, который вызывает ILogger
не несет ответственности за решения о том, как ILogger
работает. Он не знает и не хочет знать. Там написано: "Вот, возьми и запиши". Реализация регистратора отвечает за все остальное.
Я бы порекомендовал слом статики ObjectFactory
полностью и используя контейнер для разрешения и создания всех ваших классов, включая регистратор и классы, которые зависят от него, но это настолько широко, что это не очень полезно. (Это устарело, потому что это плохой шаблон. Это даже не в последней версии StructureMap.)
Все выше это рекомендация. После этого я предлагаю вариант, который на самом деле не рекомендуется, но требует меньше изменений и не дает вашим классам знать пути к файлам, потому что, пожалуйста, не делайте этого никогда.
Один из вариантов - на полпути компромисс - может быть регистрация различных именованных реализаций ILogger
, Вы можете изменить свой класс логгера так, чтобы он выглядел так:
public class FileLogger : ILogger
{
private readonly string _filePath;
public FileLogger(string filePath)
{
_filePath = filePath;
}
}
Теперь вы можете создать несколько экземпляров этого класса журнала, передавая разные пути к файлам каждому. Таким образом, это не статическое свойство, которое ограничивает вас наличием только одного пути к файлу.
Тогда вы можете зарегистрировать свои реализации, как это.
ObjectFactory.Initialize(
request =>
{
request.For<ILogger>().Singleton()
.Use<FileLogger>(() => new FileLogger("some path")).Name = "LoggerOne";
request.For<ILogger>().Singleton()
.Use<FileLogger>(() => new FileLogger("some other path")).Name = "LoggerTwo";
});
Теперь ваш класс может сказать, какой регистратор ему нужен, например так:
var logger = ObjectFactory.GetNamedInstance<ILogger>("LoggerOne");
Но, пожалуйста, тоже не делай этого. Это больше, чем я могу описать здесь очень подробно, но взгляните на внедрение зависимостей, чтобы ваши классы знали только о ILogger
и не знают или не заботятся о том, какую реализацию они получают, и ничего не говорят ей о том, как выполнять свою работу.
Вы используете свой регистратор в качестве одиночного, поэтому вы не создаете экземпляр при каждом вызове ObjectFactory.GetInstance<ILogger>();
вы просто получаете ссылку на один и тот же экземпляр регистратора, который создается один раз при первом использовании.
Если вы хотите написать в определенное место назначения, то лучшим решением будет указать место назначения в Logging
методы:
void LogError(string message,
Exception exception = null,
string destination = /*some adequate defualt value*/);
void LogMessage(string message,
string destination = /*some adequate defualt value*/);
Создание информации о состоянии в вашем экземпляре регистратора с определенным назначением может быть опасным, если вы используете регистратор одновременно с методами, которые ожидают и, следовательно, устанавливают разные пункты назначения; Вы можете в конечном итоге регистрировать вещи там, где они не должны.
Что поднимает важную проблему; поскольку вы используете логгер в своем приложении (singleton), убедитесь, что его методы безопасны для одновременного вызова, если есть вероятность, что он будет вызываться таким образом.