【问题标题】:How to properly replace 'extends', using functional programming?如何使用函数式编程正确替换“扩展”?
【发布时间】:2020-04-05 17:05:03
【问题描述】:

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

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

到目前为止,我主要使用函数取得了成功,但有一种情况我无法真正弄清楚。

当我们想要在不同对象之间重用行为时,我们通常(在 OOP 中)创建一个扩展另一个类的类。

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())

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

但是,通常当我们使用类时,子级可以访问父级的成员,并且可能需要在某些方法中使用它。例如:

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())

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

【问题讨论】:

  • 你的错误是试图用函数式编程的方式重建面向对象的习语。使用 FP 表示功能性习语。
  • @bob 这就是我所怀疑的。您对在这种情况下如何以更多的 FP 术语进行思考有什么建议吗?
  • 不要试图避开class 关键字。尽量避免可变的对象属性(即,让一切都成为readonly,因为这看起来像 Typescript)。然后,一旦您熟悉了不可变对象,您就会发现可能使 class 语法的替代方案更具吸引力的模式。
  • FP 使用algebraic data types 对域进行建模。如果您有 FP 背景,这是一种完全不同的方法,而且相当陌生。
  • "当我们有想要在不同对象之间重用的行为时,我们通常(在 OOP 中)创建一个扩展另一个类的类。" 不,我们 不要。至少我们不应该。如果你想重用代码,那就让它成为一个可重用的组件。使用组合。继承不是为了代码重用,而是为了构建你的类层次结构——“什么是什么”,而不是“什么做什么”。

标签: javascript class oop mixins composition


【解决方案1】:

前言

一个可能的解决方案背后的机制,OP 正在寻找,仍然是 OO,因为 OO 可以;毕竟,人们正在处理的是通过调用 javascript 函数进行对象组合(或对象/类型扩充)。 Eric Elliott - functional mixin - 和 Douglas Crockford - functional inheritance - 每个人都很好地解释了他们的方法。他们可能错过了命名/标签。在我看来,它应该像 基于函数的 mixin 一样简单。由于 functional 一词将不再指向或误导 »The Land of FP«,因此 JavaScript 开发人员之间的混淆会减少。 p>

JavaScript function 的强大功能来自于它的每一个功能,第一是通过创建闭包来保持范围,第二是通过this 访问上下文,并通过其调用方法之一提供前者callapply .在第 3 位,它本身是一个可以传递的一流对象,只是对完整的包进行了四舍五入。

接近

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

Eric 和 Douglas 的概念将通过应用得到尊重/承认。

在我看来,JavaScript 中的模块化可组合行为始终应该由一个函数提供,该函数既不应该通过 new 关键字调用也不应该由调用运算符调用 ...() ..,但它总是必须通过callapply 应用于/应用于其他对象/类型。

具有共享但受保护(本地范围)飞行状态的 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; }

【讨论】:

    猜你喜欢
    • 2019-12-15
    • 2021-04-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-13
    • 2014-06-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多