Рассмотрите этот сценарий. У меня есть некоторая бизнес-логика, которая время от времени потребуется, чтобы писать в журнал.
interface ILogger
{
void Log(string stuff);
}
interface IDependency
{
string GetInfo();
}
class MyBusinessObject
{
private IDependency _dependency;
public MyBusinessObject(IDependency dependency)
{
_dependency = dependency;
}
public string DoSomething(string input)
{
// Process input
var info = _dependency.GetInfo();
var intermediateResult = PerformInterestingStuff(input, info);
if (intermediateResult== "SomethingWeNeedToLog")
{
// How do I get to the ILogger-interface?
}
var result = PerformSomethingElse(intermediateResult);
return result;
}
}
Как Вы получили бы интерфейс ILogger? Я вижу две основных возможности;
Какой метод Вы предпочли бы, и почему? Или есть ли еще лучший шаблон?
Обновление: Обратите внимание, что я не должен регистрировать ВСЕ вызовы метода. Я только хочу зарегистрировать несколько (редких) событий, которые могут или не могут произойти в рамках моего метода.
Лично я использую и то, и другое.
Вот мои соглашения:
Я считаю, что это дает мне правильный баланс тестируемости. Мне немного сложнее настроить тесты для классов, использующих Service Location, чем использующих DI, поэтому именно поэтому Service Location оказывается исключением, а не правилом. Тем не менее, я последовательно использую его, поэтому нетрудно вспомнить, какой тип теста мне нужно написать.
Некоторые высказывали опасения, что DI имеет тенденцию загромождать конструкторы. Я не считаю, что это проблема, но если вы так считаете, существует ряд альтернатив, использующих DI, но избегающих параметров конструктора. Вот список методов DI Ninject: http://ninject.codeplex.com/wikipage?title=Injection%20Patterns
Вы обнаружите, что большинство контейнеров Inversion of Control имеют одинаковые функции как Ninject. Я решил показать Ninject, потому что у них самые лаконичные образцы.
Надеюсь, это будет полезно.
Изменить: Для ясности, я использую Unity и Common Service Locator . У меня есть одноэлементный экземпляр моего контейнера Unity для DI, а моя реализация IServiceLocator - это просто оболочка вокруг этого одноэлементного контейнера Unity. Таким образом, мне не нужно дважды выполнять сопоставление типов или что-то в этом роде.
Я также не считаю АОП особенно полезным, помимо трассировки. Мне больше нравится ручное ведение журнала просто из-за его наглядности.Я знаю, что большинство фреймворков для ведения журналов АОП поддерживают и то, и другое, но большую часть времени мне не нужны первые («хлеб с маслом» АОП). Конечно, это личное предпочтение.
Мое небольшое практическое правило:
Если он находится в библиотеке классов, используйте либо внедрение конструктора, либо внедрение свойств с шаблоном нулевого объекта.
Если он находится в основном приложении, используйте локатор служб (или одноэлементный).
Я считаю, что это очень хорошо применимо при использовании log4net. Вы не хотите, чтобы библиотеки классов обращались к вещам, которых может не быть, но в прикладной программе вы знаете, что регистратор будет там, а такие библиотеки, как log4net, в значительной степени основаны на шаблоне местоположения службы.
Я склонен думать о ведении журнала как о чем-то достаточно статичном, что на самом деле не требует DI. Крайне маловероятно, что я когда-либо изменю реализацию ведения журнала в приложении, тем более что каждая существующая среда ведения журнала невероятно гибкая и легко расширяемая. Это более важно в библиотеках классов, когда ваша библиотека может потребоваться для использования несколькими приложениями, которые уже используют разные регистраторы.
YMMV, конечно. DI - это здорово, но это не значит, что все нужно DI'ed.
Мы переключили все наши атрибуты ведения журнала / трассировки на PostSharp (структура AOP). Все, что вам нужно сделать, чтобы создать журнал для метода, - это добавить к нему атрибут.
Преимущества:
Проверка out this .
Регистратор - это явно служба, от которой зависит ваша бизнес-логика, и поэтому ее следует рассматривать как зависимость так же, как и с IDependency
. Вставьте регистратор в свой конструктор.
Примечание: даже несмотря на то, что АОП упоминается как способ внедрения журналирования, я не согласен с тем, что это решение в данном случае. АОП отлично подходит для отслеживания выполнения, но никогда не станет решением для ведения журнала как части бизнес-логики.
Вы можете получить другой тип, например LoggableBusinessObject
, который принимает средство ведения журнала в своем конструкторе. Это означает, что вы передаете регистратор только для объектов, которые будут его использовать:
public class MyBusinessObject
{
private IDependency _dependency;
public MyBusinessObject(IDependency dependency)
{
_dependency = dependency;
}
public virtual string DoSomething(string input)
{
// Process input
var info = _dependency.GetInfo();
var result = PerformInterestingStuff(input, info);
return result;
}
}
public class LoggableBusinessObject : MyBusinessObject
{
private ILogger _logger;
public LoggableBusinessObject(ILogger logger, IDependency dependency)
: base(dependency)
{
_logger = logger;
}
public override string DoSomething(string input)
{
string result = base.DoSomething(input);
if (result == "SomethingWeNeedToLog")
{
_logger.Log(result);
}
}
}
Я бы рекомендовал ни один из этих подходов. Лучше использовать аспектно-ориентированное программирование. Ведение журнала - это «привет мир» АОП.
Я бы предпочел одиночную службу.
Внедрение зависимости загромождает конструктор.
Если вы умеете использовать АОП, это будет лучше всего.
Д. Я бы здесь отлично поработал. Еще одна вещь, на которую стоит обратить внимание, - это AOP .