Допустим, вашему классу нужно сделать 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. В производственном коде вы не хотите, чтобы перехватчик работал для ненужных методов, поэтому используйте IInterceptorSelector вместо этого.
person
Aaronaught
schedule
07.04.2010