Добавить комментарий - CodeHelper

Добавить комментарий

В разнообразных форумах я часто вижу один и тот же вопрос: "Могу ли я создать advice для XY?". В серии последующих статей я поясню предпосылки AOP (Aspect Oriented Programming) и его реализацию в spring.net (а так же в Castle.DynamicProxy и LinFu), чтобы вы лучше понимали данную тему и смогли сами ответить на данный вопрос.

Пример: повторение операции в случае неуспеха

Вместо привычного примера с логгированием я хочу показать вам другой полезный прием: повторение операции в случае неуспеха. Чтобы не усложнять, давайте предположим, что мы вызываем метод web-сервиса, складывающий два целых числа:

CalculatorWebService calc = new CalculatorWebService("http:/...");
int sum = calc.Add(2, 5);

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

Существует пара вариантов реализации требуемого. Вероятно самым прямым подходом будет унаследовать наш класс от класса клиента web-сервиса:

public class RetryingCalculatorWebService : CalculatorWebService
{
  public RetryingCalculatorWebService(string url):base(url) {}   

  public override int Add(int x, int y)
  {
    int retries = 0;
    while (true)
    {
      try
      {
        return base.Add(x, y);
      }
      catch (Exception ex)
      {
        retries++;
        if (retries >= 3)
        {
          throw; // retry threshold exceeded, giving up// wait a second
        }
      }
    }
  }
}

И, конечно, с этим подходом есть несколько проблем. Наиболее значимые среди них:

1. Код операции повтора довольно сильно увеличивает общий объем кода, что затрудняет осознание цели метода.

public class RetryingCalculatorWebService : CalculatorWebService
{
  public RetryingCalculatorWebService(string url):base(url) {}   

  public override int Add(int x, int y)
  {
    int retries = 0;
    while(true)
    {
      try
      {
        return base.Add(x, y);
      }
      catch(Exception ex)
      {
        retries++;
        if (retries >= 3)
        {
          throw; // retry threshold exceeded, giving up
        }
        Thread.Sleep(1000); // wait a second
      }
    }
  }
}

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

alt text

Собственное решение

Более структурированный путь решения данной проблемы описан в книге банды четырех - шаблон "Декоратор". Основная его идея в "обертывании" целевого метода в дополнительный код. Таким образом вам не придется изменять существующий код. Вместо этого вы создаете "матрешку" из включающих друг друга оберточных методов, каждый из которых реализован в собственном классе. В наши дни больший приоритет отдают композиции (вместо наследования, которое применяется в подходе, описанном в книге банды четырех). Поэтому давайте создадим интерфейс для облегчения объединения требуемых поведений в одну цепочку:

public interface ICalculator
{
  int Add(int x, int y);
}

ICalculator calc = ...;
int sum = calc.Add(2, 5);

Теперь мы можем легко реализовать нашу бизнес-логику, а так же дополнительное поведение раздельно. В последствии мы можем объединить их так, как нам необходимо:

public class CalculatorWebService : ICalculator
{
  public CalculatorWebService(string url) { ... }   

  public int Add(int x, int y)
  {
    // осуществляем вызов метода web-сервиса
    return ...
  }
}

public class CalculatorRetryDecorator : ICalculator
{
  private ICalculator next;

  public CalculatorRetryDecorator(ICalculator next) { this.next = next; }

  public int Add(int x, int y)
  {
    int retries = 0;
    while (true)
    {
      try
      {
        return next.Add(x, y);
      }
      catch (Exception ex)
      {
        retries++;
        if (retries >= 3)
        {
          throw; // retry threshold exceeded, giving up
        }
        Thread.Sleep(1000); // wait a second
      }
    }
  }
}

Заметьте, как CalculatorRetryDecorator делегирует реальную работу следующему калькулятору в цепочке. Теперь, если внешние условия заставляют нас применить операцию повтора, мы с легкостью объединяем поведения:

ICalculator calc = new CalculatorRetryDecorator( new CalculatorWebService( "http://..." ) );
int sum = calc.Add(2, 5);

Также, в случае ухудшения производительности, мы можем реализовать декоратор кеширования результата вызова нашего метода и аналогичным образом добавить его в цепочку:

ICalculator calc = new CalculatorCacheDecorator( new CalculatorRetryDecorator( new CalculatorWebService( "http://..." ) ) );
int sum = calc.Add(2, 5);

Когда мы вызываем метод Add(), граф вызовов выглядит так:

alt text

И это только начало

Мы уже достигли много в разделении поведения на различные классы, но наше решение по-прежнему имеет несколько довольно сильных недостатков:

  • Когда мы добавим новый метод в ICalculator, мы будем вынуждены расширить каждый из наших декораторов.
  • Мы хотим повторно использовать реализованное в декораторах поведение для других сервисов.

Свою следующую статью я посвящу этим вопросам и покажу, как реализовать более гибкое решение.

Тут вы можете скачать исходный код для данной статьи.

Внимание! Вы собираетесь отправить информацию от имени анонимного пользователя.
v1.7.123.556
© 2009—2010 CodeHelper FAQ | О сайте | Обратная связь | История изменений | Статьи
Creative Commons LicenseМатериалы сайта распространяются под лицензией Creative Commons Attribution-Share Alike 3.0 Unported.