Насколько я понимаю создавать статическое поле, хранящее ILogger, будет неправильно, так как это влечёт за собой неявную зависимость. Тогда, получается, нужно передавать ILogger через конструктор в классы, которым требуется эта зависимость? Я нашел очень мало материала по этому поводу, не обессудьте, буду рад любому совету или ссылке. Я очень хочу соблюсти правильную архитектуру, соблюдающую принципы SOLID, я не претендую на правильное мнение, а лишь хочу получить совет. Спасибо!
Хотел бы дополнить вопрос. Вот фрагмент кода, в этот класс я передаю ILogger как зависимость, но внутри класса есть еще интерфейсы, тоже переданные как зависимости через конструктор. Мне в их методах понадобится логирование. Получается нужно в каждый метод внедрять зависимость от ILogger? Это представляется не очень красивым, как посоветуете? Лишний код убрал: class PrintCommand : ICommand { private readonly IRepository repository private readonly IDataExport dataExport private readonly ILogger logger public PrintCommand(IRepository repository, IDataExport dataExport, ILogger logger) { this.repository = repository ?? throw new ArgumentNullException(nameof(repository)) this.dataExport = dataExport ?? throw new ArgumentNullException(nameof(dataExport)) this.logger = logger ?? throw new ArgumentNullException(nameof(logger)) } public CommandResult Execute(string[] args) { // какой-то код // В методах GetProducts и GetString мне хотелось бы тоже вести лог var products = repository.GetProducts(shopId) return new CommandResult(dataExport.GetString(products)) } }
Всем большое спасибо за ответы и советы, читаю Марка Симана, скоро дополню реализацией/решением, чтобы как-то подытожить
Ответ Если строго следовать принципа SOLID, то следует взять во внимание SRP - принцип единой ответственности. Логирование - это отдельная ответственность, которой класс команды не должен заниматься. В команде вообще не должно быть логгера. Вместо этого можно сделать логирующую оболочку (паттерн декоратор). Убираем из PrintCommand логгер: class PrintCommand : ICommand { private readonly IRepository repository private readonly IDataExport dataExport public PrintCommand(IRepository repository, IDataExport dataExport) { this.repository = repository ?? throw new ArgumentNullException(nameof(repository)) this.dataExport = dataExport ?? throw new ArgumentNullException(nameof(dataExport)) } public CommandResult Execute(string[] args) { return new CommandResult(...) } } Делаем декоратор: class LoggingCommand : ICommand { private readonly ICommand command private readonly ILogger logger public LoggingCommand(ICommand command, ILogger logger) { this.command = command ?? throw new ArgumentNullException(nameof(command)) this.logger = logger ?? throw new ArgumentNullException(nameof(logger)) } public CommandResult Execute(string[] args) { // логируем входные параметры logger.Log(/* args */) var result = command.Execute(args) // логируем результат logger.Log(/* result */) return result } } Использование: IRepository repository = ... IDataExport dataExport = ... var printCommand = new PrintCommand(repository, dataExport) ILogger logger = ... var loggingPrintCommand = new LoggingCommand(printCommand, logger) Далее передаём loggingPrintCommand вместо printCommand туда, где требуется ICommand. Это легко реализуется с помощью инъекции зависимостей. В книге Марка Симана уделено внимание настройке DI для декораторов.
Кроме декораторов для этой же цели можно использовать наследование и инъекцию стратегий. Это три основных способа расширения возможностей классов. Но декораторы удобны тем, что их легко вкладывать один в другой и они дружат с DI. Однако, в декораторе нужно писать оболочки для всех методов, что утомительно и нудно. На помощь может придти AOP - перехватчики (interceptors), о чём опять же хорошо написано в книге Симана. Рекомендую!