【问题标题】:Can I use the decorator pattern to wrap a method body?我可以使用装饰器模式来包装方法体吗?
【发布时间】:2011-02-27 08:18:51
【问题描述】:

我有一堆不同签名的方法。这些方法与脆弱的数据连接交互,所以我们经常使用帮助类来执行重试/重新连接等。像这样:

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

这很好用,但它会使代码有点混乱。我更愿意做的是装饰与数据连接交互的方法,如下所示:

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

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

【问题讨论】:

  • 要求 MSFT 在 .Net 5.0 中加入一项功能?

标签: c# design-patterns decorator


【解决方案1】:

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

您可以使用属性来实现装饰器,方法是将装饰器代码放在 Attribute 类中,并使用辅助方法调用该方法,该方法使用反射调用 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# 中面向切面编程的框架。这些可能会提供您想要的。

【讨论】:

  • 您介意输入一个快速的代码示例吗?我很难想象它。
  • 太棒了,谢谢,我认为你是对的 - 与直接调用帮助程序相比,我并没有真正得到太多好处。
【解决方案2】:

所以,我这个周末刚参加了一个 AOP 会议,这里有一个使用 PostSharp 的方法:

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

然后用[MyAOPThing] 装饰方法。简单!

【讨论】:

  • 这应该是最好的答案。感谢发帖。
  • PostSharp 不是免费的
  • 这个问题没有提到价格。此外,还有免费的 Postsharp 许可证:postsharp.net/download
【解决方案3】:

如果不使用代码生成,您将无能为力。您可能可以使语法更好。

但是使用扩展方法呢?

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,因此可能值得一听。

    【讨论】:

    • 你有那个播客的链接吗?
    【解决方案5】:

    查看aspect oriented frameworks。但是请注意,虽然它们隐藏了每种方法的复杂性,但 AoP 功能的存在可能会使您的程序更难维护。这是一个权衡。

    【讨论】:

      【解决方案6】:

      您想要的似乎类似于 IoC 容器或测试运行器框架的行为,它实际上并不是从您的程序集中执行,而是运行围绕您的代码构建的动态发出的程序集。 (比我在其他答案中称此 AOP 更聪明的人)

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

      如果不评估某些现有的 AOP 框架是否可以满足您的需求,我绝不会开始尝试编写此代码。 HTH>

      【讨论】:

        【解决方案7】:

        既然您愿意为每个需要它的方法添加一行代码,为什么不直接从方法本身中调用 MyHelper,就像这样?

        protected string doStuffWithData(parameters...)
        {
             MyHelper.PerformCall( () => { doStuffWithDataCore(parameters...) });
        }
        
        private string doStuffWithDataCore(parameters...) {
            //Do stuff here
        }
        

        【讨论】:

        • 是的,这就是我目前正在做的事情。
        • 那么添加属性对你有什么作用呢?
        猜你喜欢
        • 2011-08-10
        • 1970-01-01
        • 1970-01-01
        • 2014-12-29
        • 2023-03-16
        • 2017-05-25
        • 2013-05-07
        • 2019-02-02
        相关资源
        最近更新 更多