Что на самом деле перехватчики делают с моим классом c #?

Меня попросили реализовать динамический прокси-сервер замка в моем веб-приложении asp.net, и я читал пару статей, которые я получил от Castle Project и Code Project о динамическом прокси-сервере замка в сети asp.net применение....

Обе статьи касаются создания перехватчиков, но я не могу понять, почему перехватчики используются с классами .... Почему я должен перехватывать свой класс, который ведет себя правильно?


person ACP    schedule 07.04.2010    source источник
comment
почему вас тогда вообще попросили использовать DP?   -  person Krzysztof Kozmic    schedule 07.04.2010


Ответы (2)


Допустим, вашему классу нужно сделать 3 вещи для определенной операции:

  1. Выполните проверку безопасности;
  2. Зарегистрируйте вызов метода;
  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
comment
вы в значительной степени поняли. С технической точки зрения, я бы переместил проверку правильности метода в селектор перехватчика (реализация IInterceptorSelector) - person Krzysztof Kozmic; 07.04.2010
comment
@Krzysztof Koźmic: Это ваш блог заставил меня начать работу над DP, так что я рад, что вы одобряете! И я склонен согласиться с предпочтением IInterceptorSelector, я в основном исключил его здесь, чтобы сделать этот самый простой пример более приемлемым для тех, кто не знаком с DP. - person Aaronaught; 07.04.2010
comment
Случайный вопрос для кеширования чтения использования атрибута, что именно считается лучшей практикой для захвата достаточного количества, гарантирующего уникальность? - person Chris Marisic; 08.04.2010
comment
@Chris: Когда вы говорите о кешировании чтения использования атрибута, вы имеете в виду, как именно реализована система кеширования / перехватчик? На самом деле это довольно длинная история, которая зародилась здесь: stackoverflow.com/questions/2330275/. Конечным результатом была целая библиотека примерно из 20 классов. Как я его использую, CacheAttribute принимает либо строку формата (которая применяется к параметрам), либо имя метода (для более сложной генерации ключей). Сам класс должен определять достаточно уникальные ключи для каждого кешированного метода. - person Aaronaught; 08.04.2010
comment
О, забыл упомянуть, кэширующий перехватчик использует полностью определенное имя типа как часть ключа, поэтому ключи должны быть уникальными только в рамках каждого класса. Вероятно, это хорошая практика, она похожа на то, как ASP.NET обрабатывает регистрацию сценария. - person Aaronaught; 08.04.2010

Причина, по которой вы будете использовать Castle-DynamicProxy, - это так называемое аспектно-ориентированное программирование. Это позволяет вам вставлять код в стандартный поток операций вашего кода без необходимости попадать в зависимость от самого кода.

Простой пример - как всегда, ведение журнала. Вы должны создать DynamicProxy вокруг класса, из-за которого у вас есть ошибки, поскольку он регистрирует данные, поступающие в метод, и улавливает любые исключения, а затем регистрирует исключение.

Используя перехватчик, ваш текущий код не знает, что он существует (при условии, что ваше программное обеспечение правильно построено с развязкой с интерфейсами), и вы можете изменить регистрацию своих классов с помощью инверсии контейнера управления, чтобы вместо этого использовать прокси-класс без необходимости измените одну строку в другом месте кода. Затем, когда вы исправите ошибку, вы можете отключить проксирование.

Более продвинутое использование прокси можно увидеть в NHibernate, где вся отложенная загрузка выполняется через прокси.

person Chris Marisic    schedule 07.04.2010