Допустим, вашему классу нужно выполнить 3 действия для определенной операции:
- Выполнить проверку безопасности;
- Записать вызов метода;
- Кэшировать результат.
Предположим также, что ваш класс ничего не знает о специфическом способе настройки безопасности, ведения журнала или кэширования. Вы должны зависеть от абстракций этих вещей.
Есть несколько способов сделать это. Один из способов - установить несколько интерфейсов и использовать инжектор конструктора:
public class OrderService : IOrderService
{
private readonly IAuthorizationService auth;
private readonly ILogger logger;
private readonly ICache cache;
public OrderService(IAuthorizationService auth, ILogger logger,
ICache cache)
{
if (auth == null)
throw new ArgumentNullException("auth");
if (logger == null)
throw new ArgumentNullException("logger");
if (cache == null)
throw new ArgumentNullException("cache");
this.auth = auth;
this.logger = logger;
this.cache = cache;
}
public Order GetOrder(int orderID)
{
auth.AssertPermission("GetOrder");
logger.LogInfo("GetOrder:{0}", orderID);
string cacheKey = string.Format("GetOrder-{0}", orderID);
if (cache.Contains(cacheKey))
return (Order)cache[cacheKey];
Order order = LookupOrderInDatabase(orderID);
cache[cacheKey] = order;
return order;
}
}
Это не ужасный код, но подумайте о проблемах, которые мы вводим:
Класс OrderService
не может функционировать без всех трех зависимостей. Если мы хотим сделать так, чтобы это было возможно, нам нужно начинать заполнять код с нулевых проверок везде.
Мы пишем тонну дополнительного кода для выполнения относительно простой операции (поиска заказа).
Весь этот шаблонный код должен повторяться в каждом методе, что приводит к очень большой, уродливой, подверженной ошибкам реализации.
Вот класс, который гораздо легче поддерживать:
public class OrderService : IOrderService
{
[Authorize]
[Log]
[Cache("GetOrder-{0}")]
public virtual Order GetOrder(int orderID)
{
return LookupOrderInDatabase(orderID);
}
}
В Аспектно-ориентированном программировании эти атрибуты называются Точки соединения , полный набор которых называется Срез точки .
Вместо того, чтобы фактически писать код зависимости снова и снова, мы оставляем «подсказки», что некоторые дополнительные операции должны выполняться для этого метода.
Конечно, эти атрибуты нужно превратить в код иногда , но вы можете отложить это вплоть до кода вашего основного приложения, создав прокси для OrderService
(обратите внимание, что метод GetOrder
был сделан virtual
, поскольку он должен быть переопределен для службы), и перехват метод GetOrder
.
Запись перехватчика может быть такой простой:
public class LoggingInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
if (Attribute.IsDefined(invocation.Method, typeof(LogAttribute))
{
Console.Writeline("Method called: "+ invocation.Method.Name);
}
invocation.Proceed();
}
}
И создание прокси будет:
var generator = new ProxyGenerator();
var orderService = (IOrderService)generator.CreateClassProxy(typeof(OrderService),
new LoggingInterceptor());
Это не только намного менее повторяющийся код, но полностью устраняет фактическую зависимость , потому что посмотрите, что мы сделали - у нас даже нет авторизации или Система кеширования пока нет, но система все еще работает. Мы можем просто вставить логику авторизации и кэширования позже, зарегистрировав другой перехватчик и проверив на AuthorizeAttribute
или CacheAttribute
.
Надеюсь, это объясняет "почему".
Боковая панель: Как комментирует Кшиштоф Козьмич, использование DP-перехватчика не является «лучшей практикой» для DP. В рабочем коде вы не хотите, чтобы перехватчик работал для ненужных методов, поэтому используйте взамен IInterceptorSelector .