Можно ли использовать шаблон декоратора для переноса тела метода? - PullRequest
20 голосов
/ 03 июня 2010

У меня есть куча методов с разными сигнатурами. Эти методы взаимодействуют с хрупким соединением данных, поэтому мы часто используем вспомогательный класс для выполнения повторных попыток / повторных соединений и т. Д. Примерно так:

MyHelper.PerformCall( () => { doStuffWithData(parameters...) });

И это прекрасно работает, но это может сделать код немного загроможденным. Я бы предпочел украсить методы, которые взаимодействуют с подключением к данным, так:

[InteractsWithData]
protected string doStuffWithData(parameters...)
{
     // do stuff...
}

И затем, по сути, всякий раз, когда вызывается doStuffWithData, тело этого метода передается как Action в MyHelper.PerformCall(). Как мне это сделать?

Ответы [ 7 ]

16 голосов
/ 03 июня 2010

.NET-атрибуты - это метаданные, а не декораторы / активные компоненты, которые запускаются автоматически. Нет способа добиться такого поведения.

Вы можете использовать атрибуты для реализации декораторов, помещая код декоратора в класс Attribute и вызывая метод с помощью вспомогательного метода, который вызывает метод в классе Attribute с помощью Reflection. Но я не уверен, что это было бы большим улучшением по сравнению с простым вызовом «метода-декоратора» напрямую.

"Декоратор-Атрибут":

[AttributeUsage(AttributeTargets.Method)]
public class MyDecorator : Attribute
{
    public void PerformCall(Action action)
    {
       // invoke action (or not)
    }
}

Метод:

[MyDecorator]
void MyMethod()
{
}

Использование:

InvokeWithDecorator(() => MyMethod());

Вспомогательный метод:

void InvokeWithDecorator(Expression<Func<?>> expression)
{
    // complicated stuff to look up attribute using reflection
}

Посмотрите основы для Аспектно-ориентированного программирования на C #. Они могут предложить то, что вы хотите.

16 голосов
/ 06 июня 2010

Итак, я только что пошел на сессию AOP в эти выходные, и вот способ сделать это с PostSharp:

[Serializable]
public class MyAOPThing : MethodInterceptionAspect
{
    public override void OnInvoke(MethodInterceptionArgs args)
    {
        Console.WriteLine("OnInvoke! before");
        args.Proceed();
        Console.WriteLine("OnInvoke! after");
    }
}

А затем украсить методы с помощью [MyAOPThing]. Легко!

4 голосов
/ 03 июня 2010

Без использования генерации Годе, вы не можете сделать много против этого. Возможно, вы могли бы улучшить синтаксис.

А как насчет использования метода расширения?

class static MyHelper
{
  Wrap<T>(this object service, Action<T> action)
  {
    // check attribute and wrap call
  }

}

использование:

RawFoo foo = ...
foo.Wrap(x => x.doStuffWithData(parameters...));

Это тривиально, но вы не можете убедиться, что Wrap был использован.

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

class Decorator<T>
{
    private T implementor;

    Decorator(T implementor)
    {
      this.implementor = implementor;
    }

    void Perform<T>(Action<T> action)
    {
      // check attribute here to know if wrapping is needed
      if (interactsWithData)
      {
        MyHelper.PerformCall( () => { action(implementor) });
      }
      else
      {
        action(implementor);
      }
    }
}

static class DecoratorExtensions
{
    public static Decorator<T> CreateDecorator<T>(T service)
    {
      return new Decorator<T>(service);
    }
}

Использование:

// after wrapping, it can't be used the wrong way anymore.
ExtendedFoo foo = rawFoo.CreateDecorator();
foo.Perform(x => x.doStuffWithData(parameters...));
3 голосов
/ 03 июня 2010

Этот тип проблем в значительной степени решает АОП (аспектно-ориентированное программирование). Такие инструменты, как PostSharp, могут обеспечить сквозные проблемы, переписав скомпилированный код. Подкаст Скотта Хансельмана недавно обсуждал АОП, так что, возможно, стоит послушать.

1 голос
/ 03 июня 2010

Кажется, что то, что вы хотите, похоже на поведение контейнера IoC или среды выполнения тестов, когда на самом деле она не выполняется из вашей сборки, а выполняет динамически генерируемую сборку, построенную вокруг вашего кода. (Более умные люди, чем я назвал этот АОП в других ответах)

Так что, возможно, в заглушке для вашего приложения вы можете сканировать другие сборки, создавать эти испускаемые сборки (которые вызывают MyHelper.PerformCall с телом оформленных методов), а затем ваша программа запускается для испускаемого кода.

Ни в коем случае я не буду начинать с того, чтобы попытаться написать это без оценки того, может ли какая-то существующая инфраструктура AOP выполнить то, что вам нужно. НТН>

1 голос
/ 03 июня 2010

Проверьте аспектно-ориентированные рамки .Но имейте в виду, что, хотя они скрывают сложность в каждом методе, наличие функций AoP может усложнить поддержку вашей программы.Это компромисс.

0 голосов
/ 03 июня 2010

Видя, что вы готовы добавить строку кода к каждому методу, который нуждается в этом, почему бы просто не сделать вызов MyHelper из самого метода, как этот?

protected string doStuffWithData(parameters...)
{
     MyHelper.PerformCall( () => { doStuffWithDataCore(parameters...) });
}

private string doStuffWithDataCore(parameters...) {
    //Do stuff here
}
...