我能否使用装饰器模式包装一个方法体?

29

我有一堆签名不同的方法。这些方法与一个易错的数据连接进行交互,因此我们经常使用辅助类来执行重试/重新连接等操作。就像这样:

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

这样做是有效的,但会让代码看起来有点混乱。我更喜欢装饰与数据连接交互的方法,就像这样:

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

每当调用doStuffWithData时,该方法的主体将作为Action传递给MyHelper.PerformCall()。 我该怎么做?


5
请问是否希望微软公司在 .Net 5.0 中添加一项功能? - Hamish Grubijan
7个回答

22

我刚刚参加了一个AOP课程,这是使用PostSharp的方法:

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

然后用 [MyAOPThing] 装饰方法。 很容易!


2
这应该是最好的答案。感谢您的发布。 - S.C.
6
PostSharp不是免费的。 - Andreas Merz
4
题目并没有提到价格。此外,Postsharp 也提供免费的许可证:https://www.postsharp.net/download - Matthew Groves

19

.NET属性是元数据,不是装饰器/自动调用的活动组件。没有办法实现这种行为。

您可以使用属性来实现装饰器,方法是将装饰器代码放入Attribute类中,并使用反射调用帮助器方法调用Attribute类中的方法。但我不确定这是否比直接调用“装饰器方法”有很大的改进。

"Decorator-Attribute":

[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# 中的面向切面编程框架,这些框架可能会提供你需要的功能。


你能否快速打出一段代码示例呢?我很难想象它是什么样子的。 - Matthew Groves
1
太棒了,谢谢,我认为你是对的 - 直接调用帮助程序并没有给我带来太多好处。 - Matthew Groves

4

如果没有使用代码生成,你就无法对其做太多事情。你可能可以使语法更好。

但是使用扩展方法呢?

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...));

4

这种类型的问题是AOP(面向切面编程)旨在解决的。诸如PostSharp之类的工具可以通过重写编译后的代码来提供横切关注点。Scott Hanselman的播客最近讨论了AOP,所以值得一听。


1
你有那个播客的链接吗? - Patrik Iselind

1

看看面向切面编程框架。但要注意,虽然它们在每个方法中隐藏了复杂性,但AoP功能的存在可能会使您的程序更难维护。这是一个权衡。


1

看起来你想要的类似于IoC容器或测试运行框架的行为,它并不是从你的程序集中实际执行,而是运行一个围绕你的代码构建的动态发射程序集。(比我聪明的人在其他答案中称之为AOP)

因此,也许在你的应用程序存根中,你可以扫描其他程序集,构建那些发射程序集(这些程序集调用带有装饰方法体的MyHelper.PerformCall),然后你的程序针对发射代码运行。

绝不要开始尝试编写此功能而没有评估某些现有的AOP框架是否能够完成你所需的功能。希望对你有所帮助>


-1

看到你愿意在每个需要它的方法中添加一行代码,为什么不直接在方法内部调用MyHelper呢?像这样:

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

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

是的,那正是我目前正在做的。 - Matthew Groves
1
那么,为什么要添加这个属性呢? - Sijin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接