将函数作为参数传递给其他函数,不好的做法?


40

我们一直在改变AS3应用程序与后端通信的方式,并且正在实现REST系统以替换旧系统。

不幸的是,开始工作的开发人员现在正在休长期病假,并且已移交给我。在过去的一周左右的时间里,我一直在使用它,并且我了解该系统,但是有一件事让我感到担心。函数似乎有很多传递给函数。例如,调用服务器的类具有一个函数,该函数将在过程完成且已处理错误时调用并传递对象。

它给我一种“糟糕的感觉”,让我感觉这是一种可怕的做法,我可以想到一些原因,但是在我建议对系统进行重新设计之前,我需要一些确认。我想知道是否有人对这个可能的问题有任何经验?


22
为什么会这样呢?您对函数式编程有经验吗?(我假设不会,但您应该看看)
Phoshi

8
然后考虑这一课!学术界教授的知识和实际有用的知识通常很少有重叠。那里有大量不符合这种教条的编程技术。
Phoshi 2014年

32
将功能传递给其他功能是一个基本概念,当一种语言不支持该功能时,人们会竭力创建琐碎的“对象”,其唯一目的是将所讨论的功能作为权宜之计。
2014年

36
在我的时代,我们称这些传递函数为“回调”
Mike

Answers:


84

没问题

这是一种已知的技术。这些是高阶函数(以函数为参数的函数)。

这种功能也是功能编程中的基本构建块,并在Haskell等功能语言中得到了广泛使用。

这样的功能不是坏的还是好-如果您从未遇到过这些概念和技术,一开始它们可能很难理解,但它们可能非常强大,并且是工具带中的好工具。


为此,我对函数式编程没有真正的经验,因此我将对其进行研究。它只是坐得不好,因为据我所知,当您将某物声明为私有函数时,只有该类才有权访问它,而公共类应引用该对象来调用。我会仔细研究一下,希望对它会有所帮助!
艾略特·布莱克本


8
@BlueHat如果要控制使用方式,则声明为私有,如果该使用用作特定功能的回调,那么就很好了
棘手怪胎2014年

5
究竟。将其标记为私有意味着您不希望其他人在任何时间进入并通过名称访问它。将私有函数传递给其他人,因为您特别希望他们以他们为该参数编写文档的方式来调用它,这绝对没问题。它与将私有字段的值作为参数传递给其他函数没有什么不同,这大概不会对您不利:-)
Steve Jessop 2014年

2
值得注意的是,如果您发现自己编写的函数以构造函数参数作为具体类, tm 可能做错了
2014年

30

它们不仅用于函数式编程。它们也可以称为回调

回调是一段可执行代码,将其作为参数传递给其他代码,该代码应在方便的时候回调(执行)该参数。调用可以像在同步回调中​​那样立即进行,也可以在稍后的时间(如异步回调中)进行。

考虑一下异步代码。您传入一个函数,例如,将数据发送给用户。仅在代码完成后,您才使用响应结果调用此函数,然后该函数将其用于将数据发送回用户。这是一种观念转变。

我写了一个库,可以从种子箱中检索Torrent数据。您正在使用非阻塞事件循环执行此库并获取数据,然后将其返回给用户(例如,在websocket上下文中)。假设您有5个人在此事件循环中连接,并且其中一个请求获取某人的torrent数据停滞。那会阻塞整个循环。因此,您需要异步考虑并使用回调-循环继续运行,并且“将数据返回给用户”仅在函数完成执行后运行,因此无需等待。开火,算了。


1
+1用于使用回调术语。我比“高阶函数”更经常遇到这个术语。
罗德尼·舒勒2014年

我喜欢这个答案,因为OP似乎是在专门描述回调,而不仅仅是一般意义上的高阶函数:“例如,调用服务器的类接受了一个函数,该函数随后将调用并传递一个对象该过程何时完成以及是否已处理错误等。”
2014年

11

这不是一件坏事。实际上,这是一件非常好的事情。

将函数传递给函数对于编程非常重要,以至于我们发明了lambda函数作为简写。例如,可以将lambda与C ++算法结合使用,以编写非常紧凑而又富有表现力的代码,从而使通用算法能够使用局部变量和其他状态来执行诸如搜索和排序之类的操作。

面向对象的库也可能具有回调,这些回调本质上是指定少量函数(理想情况下是一个函数,但并非总是如此)的接口。然后,可以创建一个实现该接口的简单类,并将该类的对象传递给函数。这是事件驱动编程的基石,其中框架级代码(甚至可能在另一个线程中)需要调用对象以响应用户操作而更改状态。Java的ActionListener接口就是一个很好的例子。

从技术上讲,C ++函子也是一种回调对象,它利用语法糖(operator()())做同样的事情。

最后,有一些C样式的函数指针仅应在C中使用。我将不做详细介绍,出于完整性的考虑,我仅提及它们。上面提到的其他抽象要好得多,应该在具有它们的语言中使用。

其他人提到了函数编程,以及在这些语言中传递函数是多么自然。Lambda和回调是过程和OOP语言模仿的方式,它们非常强大且有用。


同样,在C#中,委托和lambda都被广泛使用。
邪恶的狗馅饼

6

如前所述,这不是坏习惯。这仅仅是将责任分离和分离的一种方式。例如,在OOP中,您将执行以下操作:

public void doSomethingGeneric(ISpecifier specifier) {
    //do generic stuff
    specifier.doSomethingSpecific();
    //do some other generic stuff
}

通用方法将特定的任务(它一无所知)委派给另一个实现接口的对象。通用方法仅知道此接口。在您的情况下,此接口将是一个要调用的函数。


1
这个习惯用法只是将函数包装在接口中,完成了与传递原始函数非常相似的事情

是的,我只是想证明这并非不常见的做法。
菲利普·穆里

3

通常,将函数传递给其他函数没有任何问题。如果您要进行异步调用并希望对结果做些事情,则需要某种回调机制。

简单回调有一些潜在的缺点:

  • 进行一系列调用可能需要深度嵌套回调。
  • 错误处理可能需要为一系列呼叫中的每个呼叫重复。
  • 协调多个呼叫很尴尬,例如同时进行多个呼叫,然后在完成所有呼叫后再执行一些操作。
  • 没有一般的取消一组呼叫的方法。

使用简单的Web服务,您的工作方式可以正常工作,但是如果您需要更复杂的呼叫排序,它将变得很尴尬。虽然有一些替代方案。以JavaScript为例,已经向使用Promise转变(javascript Promise真是太好了)。

它们仍然涉及将函数传递给其他函数,但是异步调用返回的值本身需要回调,而不是直接采用回调。这为将这些调用组合在一起提供了更大的灵活性。这样的事情可以在ActionScript中轻松实现。


我还要补充一点,对回调的不必要使用会使任何阅读代码的人都难以遵循执行流程。显式调用比在代码的远处设置的回调更容易理解。(这是抢救的突破!)尽管如此,这还是在浏览器和其他基于事件的系统中触发事件的方式。那么我们需要了解事件发射器的策略,以了解何时以及按什么顺序调用事件处理程序。
joeytwiddle
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.