【问题标题】:Chain of responsibility using Func使用 Func 的责任链
【发布时间】:2018-03-17 13:41:26
【问题描述】:

我正在使用System.Func<T, T> 创建一个责任链管道,其中管道中的每个函数都包含对下一个函数的引用。

在构建管道时,我无法通过引用传递内部函数,因为它会由于重新分配管道函数而引发 StackOverflowException,例如:

Func<string, Func<string, string>, string> handler1 = (s, next) => {
    s = s.ToUpper();
    return next.Invoke(s);
};

Func<string, string> pipeline = s => s;
pipeline = s => handler1.Invoke(s, pipeline);

pipeline.Invoke("hello"); // StackOverFlowException

我可以通过闭包解决这个问题:

Func<string, Func<string, string>, string> handler1 = (s, next) => {
    s = s.ToUpper();
    return next.Invoke(s);
};

Func<Func<string, string>, Func<string, string>> closure = 
    next => s => handler1.Invoke(s, next);

Func<string, string> pipeline = s => s;
pipeline = closure.Invoke(pipeline);

pipeline.Invoke("hello");

但是,我想知道是否有更有效的方法来构建这个函数链,也许使用表达式?

【问题讨论】:

  • 只是想知道,如果您知道问题是重新分配pipeline(这是正确的),为什么不创建一个新变量?
  • 我的额外管道支持任意数量的函数。声明一个新变量并没有帮助,因为从根本上讲,您仍然必须通过引用传递以前的函数。
  • 忽略上述,新变量是解决方案 - 我会责怪睡眠不足:)
  • 很高兴为您提供帮助。你介意用你的解决方案发布你的答案吗?我更喜欢发布的 3 个答案中的一个

标签: c# chain-of-responsibility


【解决方案1】:

那又如何?这样您就可以构建任意长度的链。

void Main()
{
    Func<string, string> f1 = x => x.Replace("*", string.Empty);
    Func<string, string> f2 = x => x.Replace("--", string.Empty);
    Func<string, string> f3 = x => x.ToUpper();

    //Func<string, string> pipeline = x => f3(f2(f1(x)));
    Func<string, string> pipeline = Pipeline(f1, f2, f3);

    pipeline.Invoke("te-*-st").Dump(); // prints "TEST"
}

Func<T, T> Pipeline<T>(params Func<T, T>[] functions)
{
    Func<T, T> resultFn = x => x;

    for (int i = 0; i < functions.Length; i++)
    {
        Func<T, T> f = functions[i];
        Func<T, T> fPrev = resultFn;
        resultFn = x => f(fPrev(x));
    }

    return resultFn;
}

【讨论】:

  • 这实际上不是责任链管道,但您的回答指出了定义局部变量以引用先前函数的简单解决方案。我早该想到这一点的。
【解决方案2】:

使用表达式,由于编译表达式的成本,可以保证流程的“构建”部分效率较低,可能比链接Funcs 慢至少两个数量级。

深入研究表达式 - 管道的元素是表达式本身而不是 Funcs,可用于通过重写创建更高效​​的运行时实现。设置速度较慢,但​​基本上每次你都会收到一个元素的表达式,例如:

Expression<Func<string, Func<string, string>, string>> handler1 = (s, next) => 
    next.Invoke(s.ToUpper());

您重写它,以便应该在 next 中的任何内容的主体直接内联到表达式树中 next.Invoke(...) 出现的位置。

这确实限制了您使用表达式主体元素(实际上,您只需让任何复杂处理程序的主体调用辅助函数,而不是执行它们需要内联执行的任何工作)。

试图在某个地方找到一个这样的例子,但我想不出一个好的例子。祝你好运!

【讨论】:

  • 我发现了一篇很好的帖子,讨论了类似的here,所以可以深入研究一下。
【解决方案3】:

在我看来,责任链就像一个链表。通常,它会创建一个类来封装每个处理程序,该处理程序包含对链中下一个处理程序的引用。但是如果你想使用Func 风格,我们可以使用程序风格做类似的事情:

在这里演示:https://dotnetfiddle.net/LrlaRm

public static void Main()
    {
        Func<string, string> handler1 = (s) => {
            s = s.ToUpper();
            return s;
        };

        Func<string, string> handler2 = (s) => {
            s = s.TrimStart();
            return s;
        };


        Func<string, string> chain = ChainBuilder(handler1, handler2);

        Console.WriteLine(chain("    hello"));
    }

    static Func<Func<string, string>, Func<string, string>, Func<string, string>> ChainBuilder = (f1, f2) => s => {
        s = f1(s);
        if (f2 != null) {
            s = f2(s);
        }

        return s;
    };

我正在做的是创建一个高阶函数来构建链。 另一个使用相同想法链接 3 个处理程序的演示:https://dotnetfiddle.net/ni0DKL

但是,我建议为此创建一个类:https://dotnetfiddle.net/CsVpzh。从封装和抽象的角度来看,它更好,并且易于扩展以向每个处理程序添加特定的配置。

【讨论】:

  • 严格来说,您的Func 版本没有实现责任链,因为每个处理程序不负责确定是否应该执行下一个处理程序。我同意显式处理程序类型的封装,我只是想提供这两个选项,因为我仍然能够提供基本上回退到 Func 的处理程序接口
  • @Ben Foster:使用Func 实现责任链似乎没有简单的解决方案。因为在这种模式中,链中的每个处理程序都需要为stateful(存储引用),但Funcstateless。处理程序在确定是否应该执行下一个处理程序的高阶函数中引用,这是函数式编程风格:en.wikipedia.org/wiki/Functional_programming。接受的答案也是相同的想法,但写作方式不同。
  • @Ben Foster:如果您需要不同的方法来确定是否应该执行下一个处理程序,您可以编写具有不同逻辑的不同链构建器来确定如何执行下一个处理程序。 IMO,我们应该像上面那样编写一个类,这是处理这个问题的最自然方法
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-26
  • 1970-01-01
相关资源
最近更新 更多