【问题标题】:How should I pass data between small functions - via closures or via properties of an object?我应该如何在小函数之间传递数据 - 通过闭包或通过对象的属性?
【发布时间】:2016-10-26 20:36:24
【问题描述】:

我有一个复杂的业务操作,例如删除用户帐户。它包含多个连接的步骤,并且必须跟踪步骤之间的某些状态。编写此action 的更好方法是什么?

我看到更多functional 方法,如下所示。

function someAction(someParam, anotherParam, callback) {

    async.waterfall([
            step1,
            step2,
            step3,
            step4
            ],callback
    );

    function step1(p,cb){/**use someParam and anotherParam here via closure*/}
    function step2(p,cb){/**...*/}
    function step3(p,cb){/**...*/}
    function step4(p,cb){/**...*/}
};

someAction('value', 1241, (err)=>{/**...*/});

我不喜欢这种方法的一点是,所有内容都在单个函数的范围内定义(此处为 someAction)。

我找到了一种更object-oriented 的方式来更具可读性。状态和stepX 函数并不是真正私有的——有时它便于测试。

function SomeAction(someParam, anotherParam){
    //private state
    this._someParam = someParam;
    this._anotherParam = anotherParam;
};

SomeAction.prototype._step1 = function(p, cb){
    //use this._someParam and this._anotherParam
};

SomeAction.prototype._step2 = function(p, cb){
    //use this._someParam and this._anotherParam
};

SomeAction.prototype._step3 = function(p, cb){
    //use this._someParam and this._anotherParam
};

SomeAction.prototype._step4 = function(p, cb){
    //use this._someParam and this._anotherParam
};

//public api
SomeAction.prototype.execute = function(callback) {
    async.waterfall([
                this._step1,
                this._step2,
                this._step3,
                this._step4
            ],callback
    )
};

new SomeAction('value', 1241).execute((err)=>{/**...*/})

它们之间有性能差异吗? Node.js 中推荐的方法是什么?每次我以功能方法调用someAction 时,是否真的 - 所有stepX 函数都必须从头开始定义?

【问题讨论】:

  • 推荐的方法是对您来说更具可读性并且符合您的要求(可测试性)的方法。性能差异可以忽略不计。
  • 最好的解决方案是使用 Promise。既是对象又是功能。
  • 函数式并不意味着将整个逻辑放在一个函数中。这意味着设计每个具有单一目的的小功能,然后将这些功能组合成更复杂的功能。使用函数式范例,您不会获得封装,而是代码重用和由于没有副作用而提高了可维护性。
  • @LUH3417 感谢您的解释!我介绍的两种方法都由许多小函数组成(我喜欢这种风格——鲍勃叔叔推荐的)。问题是我应该如何在这些小函数之间传递数据 - 通过闭包或通过对象的属性。鲍勃叔叔认为,类是数据和操作它的方法的包。
  • 现在我明白了。我很困惑,因为您在包装器内定义了 stepX 函数(当然要关闭它们),因此不能再重用它们。您可以将不断变化的状态作为附加参数传递,并使用 state monad 将其抽象出来。如果共享状态没有改变,那么 reader monad 是一个替代方案。另一个问题是您是否使用不可变数据。最后,我认为这个问题太宽泛了。

标签: javascript node.js oop design-patterns functional-programming


【解决方案1】:

这不是一个完整的答案,而是回答了您的间接问题:

问题是我应该如何在这些小函数之间传递数据 - 通过闭包或通过对象的属性。

还有第三种方法。如果您熟悉 OO 设计,那么您可能熟悉 命令模式 的概念。也就是说,您需要动态构造一个函数,但这是不可能的,因此您可以使用一种方法创建一个对象,然后您可以根据对象的属性对其进行自定义。

在函数式编程中,这种设计模式等价于函数工厂模式。基本上你写一个函数来生成另一个函数。

因此,您希望将 someParamanotherParam 传递给异步函数,但您希望能够在 someAction 函数之外编写该函数。你可以这样做:

function someAction (someParam, anotherParam, callback) {

    async.waterfall([
        make_step1(someParam,anotherParam),
        make_step2(someParam,anotherParam)
        /* ... */
        ],callback
    );
}

function make_step1 (someParam, anotherParam) {
    return function (p, cb) {
        // use someParam and anotherParam here
    }
}

function make_step2 (someParam, anotherParam) {
    return function (p, cb) {
        // use someParam and anotherParam here
    }
}

// ...

这消除了您对函数代码提出的主要反对意见:您不再需要在 someAction() 中定义所有步骤函数,这使它看起来更类似于 OO 代码。

每次调用someAction() 时,这仍然会创建所有步进函数的新实例(现在仅从 make 函数返回)。但是解释器不必再次编译这些函数。相反,只会创建一个新的闭包(将闭包视为与程序堆栈断开链接的冻结堆栈帧)。

【讨论】:

  • V8 使用 JIT - 你知道function factory 会影响性能吗?
  • @JanOsch:测试的唯一方法是基准测试。返回的函数实际上只编译一次。因此它将具有与对象相同的编译性能。理论上,对象和闭包在运行时都是 malloc 的,因此它们在内存分配/解除分配方面应该具有相同的速度性能。闭包在调用时确实有一个稍微复杂的堆栈管理来执行,但是对象在创建时有一个稍微复杂的内存分配(检查继承等)。因此,了解哪个更适合您的用例的唯一方法是进行基准测试
【解决方案2】:

您可以使用 Promises 来实现如下所示的样式:

var Promise = require('promise');

//** Execute Program **//
main();

/**
 * Do your async logic flow here
 */
function main() {
  step1()
    .then(step2) //Wait for step1 to finish, and pass the response directly to step2
    .then(function(res) {
      // Do something with res (the return from step2)
      // Async execute step3 & step4
      var promises = [
        step3(),
        step4()
      ];

      // Wait for step3 & step4 to finish:
      Promise.all([promises[0], promises[1]]).then(function(values) {
        console.log(values[0]); //Value from step3
        console.log(values[1]); //Value from step4
      }).catch(function(e) {
        console.log(e); //Reject or thrown errors from steps3 or 4
      });
    }).catch(function(e) {
      console.log(e); //Reject or thrown errors from steps1 or 2
    });
}

function step1() {
  return new Promise(resolve, reject) {
    //some async task here
    resolve('step1'); 
    //reject('step1'); //Or trigger a .catch (i.e. this function failed)
  });
}

function step2() {
  return new Promise(resolve, reject) {
    //some async task here
    resolve('step2'); 
    //reject('step2'); //Or trigger a .catch (i.e. this function failed)
  });
}

function step3() {
  return new Promise(resolve, reject) {
    //some async task here
    resolve('step3'); 
    //reject('step3'); //Or trigger a .catch (i.e. this function failed)
  });
}

function step4() {
  return new Promise(resolve, reject) {
    //some async task here
    resolve('step4'); 
    //reject('step4'); //Or trigger a .catch (i.e. this function failed)
  });
}

【讨论】:

    【解决方案3】:

    您可以创建阶跃函数的柯里化版本并将它们粘贴到瀑布中。

    function step1(arg1, arg2, cb){
      // Fuction body...
    }
    
    // other steps would be defined here...
    
    function step4(arg1, cb){
      // fuction body...
    }
    
    curried_step1 = step1.bind(undefined, 'junk', 'garb');
    // Other steps curried here...
    curried_step4 = step4.bind(undefined, 'something');
    
    async.waterfall([
                curried_step1,
                curried_step2,
                curried_step3,
                curried_step4
            ],callback
    );
    

    另一种方法是将您的数据和状态包装到一个对象中(代替真正的 monad),并使用该对象传递您需要的内容。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-07-07
      • 2018-07-31
      • 2021-12-30
      • 2012-08-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-08-17
      相关资源
      最近更新 更多