如何使用函数式编程正确替换 '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
访问上下文并通过以下方法之一提供前者它的调用方法 call
或 apply
。第 3 个是第一个 class 对象本身,它可以传递给整个包。
方法
OP 关于如何实现模块化行为的问题,该行为依赖于由另一个行为封装的状态,可以通过传递此状态来解决。这种状态不一定要公开展示。
Eric 和 Douglas 的概念将 honored/acknowledged 按字面意思 应用 它。
在我看来,JavaScript 中的模块化可组合行为始终应由既不应通过 new
关键字调用也不应由调用运算符调用的函数提供... ()
..,但必须始终通过 call
或 apply
.
应用 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; }
我正在研究如何在 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
访问上下文并通过以下方法之一提供前者它的调用方法 call
或 apply
。第 3 个是第一个 class 对象本身,它可以传递给整个包。
方法
OP 关于如何实现模块化行为的问题,该行为依赖于由另一个行为封装的状态,可以通过传递此状态来解决。这种状态不一定要公开展示。
Eric 和 Douglas 的概念将 honored/acknowledged 按字面意思 应用 它。
在我看来,JavaScript 中的模块化可组合行为始终应由既不应通过 new
关键字调用也不应由调用运算符调用的函数提供... ()
..,但必须始终通过 call
或 apply
.
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; }