如何使用函数式编程正确替换 'extends'?

How to properly replace 'extends', using functional programming?

我正在研究如何在 javascript 中应用函数式编程,并且正在尝试避免使用 class 关键字。

我不喜欢为了遵循某种范式而采取极端措施,但我很好奇是否可以在不使用 classes 的情况下编写出好的代码。

到目前为止,我主要使用函数取得了成功,但有一种情况我真的弄不明白。

当我们有想要在不同 objects 之间 re-use 的行为时,我们通常(在 OOP 中)创建一个 class 来扩展另一个 class。

class FlyingThing {
   private let _isFlying = false

   fly() {
       _isFlying = true
       return this
   }

   land() {
       _isFlying = false
       return this
   }

   isFlying() {
      return _isFlying
   }
}

class Duck extends FlyingThing {
   quack() {
       return 'Quack!'
   } 
}

const duck = new Duck()
console.log(duck.fly().quack())

现在介绍函数方法...

示例取自:https://medium.com/javascript-scene/functional-mixins-composing-software-ffb66d5e731c

const flying = o => {
  let isFlying = false
  return Object.assign({}, o, {
    fly () {
      isFlying = true
      return this
    },
    isFlying: () => isFlying,
    land () {
      isFlying = false
      return this
    }
  })
}

const quacking = quack => o => Object.assign({}, o, {
  quack: () => quack
})

const createDuck = quack => quacking(quack)(flying({}))
const duck = createDuck('Quack!')
console.log(duck.fly().quack())

好的,我喜欢这个主意;我们正在使用组合,并且我们在任何 parents 和 children 之间没有紧密耦合。酷

然而,通常当我们使用classes时,child可以访问parent的成员,并且可能需要在某些方法中使用它。例如:

class FlyingThing {
   private let _isFlying = false

   fly() {
       _isFlying = true
       return this
   }

   land() {
       _isFlying = false
       return this
   }

   isFlying() {
      return _isFlying
   }
}

class Duck extends FlyingThing {
   quack() {
       return 'Quack!'
   }

   // New method - Depends on 'isFlying' defined in parent
   layEgg() {
       if(isFlying) return
       return 'Laying egg...'
   }
}

const duck = new Duck()
console.log(duck.fly().quack())

所以问题是,我们如何仅使用函数优雅地解决这个问题?

前言

可能的解决方案背后的机制,OP 正在寻找,仍然是 OO,因为 OO 可以;毕竟,一个人正在处理的是通过调用 javascript 函数的对象组合(或 object/type 扩充)。 Eric Elliott - functional mixin - and Douglas Crockford - functional inheritance - 每个人都很好地解释了他们的方法。他们可能错过了 naming/labeling。在我看来,它应该像 function based mixin 一样简单。 JavaScript 开发人员之间的混淆会减少,因为术语 functional 将不再指向或误导 » FP 之乡«.

JavaScript function 的强大功能来自于它的每一项能力,首先是通过创建闭包来保留作用域,其次是通过 this 访问上下文并通过以下方法之一提供前者它的调用方法 callapply。第 3 个是第一个 class 对象本身,它可以传递给整个包。

方法

OP 关于如何实现模块化行为的问题,该行为依赖于由另一个行为封装的状态,可以通过传递此状态来解决。这种状态不一定要公开展示。

Eric 和 Douglas 的概念将 honored/acknowledged 按字面意思 应用 它。

在我看来,JavaScript 中的模块化可组合行为始终应由既不应通过 new 关键字调用也不应由调用运算符调用的函数提供... () ..,但必须始终通过 callapply.

应用 to/onto 其他 objects/types

OP 的示例代码具有共享但受保护(本地作用域)的飞行状态...

function withFlightStateAlteringFlightCapability(state) {
  const flightCapableType = this;

  flightCapableType.fly = () => {
    state.flying = true;
    return flightCapableType;
  };
  flightCapableType.land = () => {
    state.flying = false;
    return flightCapableType;
  };
  flightCapableType.isFlying = () => state.flying;

  return flightCapableType;
}

function withFlightStateDependedEggLayingBehavior(state) {
  const oviparousType = this;

  oviparousType.layEgg = () => {
    let returnValue;

    // if (!this.isFlying()) {
    if (!state.flying) {
      returnValue = 'Laying egg...'
    }
    return returnValue;
  };
  return oviparousType;
}

function withMetaBehavior(label, behavior) {
  this[label] = behavior;
}

class Duck {
  constructor() {

    // - glue code wrapped by constructor.
    // - type will feature a class signature.
    // - `state` gets preserved by the closure that is created with each instantiation.

    // local state (shared and protected)
    const state = {
      flying: false
    };
    const duck = this;

    withFlightStateAlteringFlightCapability.call(duck, state);
    withFlightStateDependedEggLayingBehavior.call(duck, state);
    withMetaBehavior.call(duck, 'quack', () => 'Quaaack...Quaaack...');
  }
}
const duck = new Duck;

function createDuckAlikeType() {

  // - glue code wrapped by factory function.
  // - type will be an augmented but ordinary `Object` type.
  // - `state` gets preserved by the closure that is created with each invocation of the factory.

  // local state (shared and protected)
  const state = {
    flying: false
  };
  const type = {};

  withFlightStateAlteringFlightCapability.call(type, state);
  withFlightStateDependedEggLayingBehavior.call(type, state);
  withMetaBehavior.call(type, 'quack', () => 'Quack!');

  return type;
}
const duckAlikeType = createDuckAlikeType();

console.log('composed "real duck" : ', duck);
console.log('composed "duck alike type" : ', duckAlikeType);

console.log('\nduck.fly() ...');
duck.fly();

console.log('\nduck.isFlying() ? ', duck.isFlying());
console.log('duckAlikeType.isFlying() ? ', duckAlikeType.isFlying());

console.log('\nduck.layEgg() ? ', duck.layEgg());
console.log('duckAlikeType.layEgg() ? ', duckAlikeType.layEgg());

console.log('\nduck.land().layEgg() ? ', duck.land().layEgg());
console.log('duckAlikeType.fly().layEgg() ? ', duckAlikeType.fly().layEgg());

console.log('\nduck.isFlying() ? ', duck.isFlying());
console.log('duckAlikeType.isFlying() ? ', duckAlikeType.isFlying());

console.log('\nduck.quack() ? ', duck.quack());
console.log('duckAlikeType.quack() ? ', duckAlikeType.quack());
.as-console-wrapper { max-height: 100%!important; top: 0; }