我试图围绕对象链接其他对象来编写节点模块。这是我到目前为止(灵感来自this answer):
'use strict'
// Composable prototype object
var parent = {
publicVar: 1,
doSomething() {
return externalMethod(this.publicVar) + 10
}
}
// Composable prototype object
var child = {
doSomethingChild() {
return this.publicVar + 20
}
}
// an external method
function externalMethod(arg) {
return arg
}
// the parent factory
function Parent() {
let privateVar = 2
return Object.assign({
getPrivate() {
return privateVar
}
}, parent)
}
// the child factory
function Child() {
let privateVar = 4
let parent = Parent() // call to the Parent factory
return Object.assign(parent, child, {
getPrivateChild() {
return privateVar
}
})
}
// Node export
module.exports = {
Parent: Parent(),
Child: Child()
}
稍后,我会要求这样的模块:
Parent = require('./my-module').Parent
Child = require('./my-module').Child
Parent.getPrivate() // 2
Parent.doSomething() // 11
Child.getPrivateChild() // 4
Child.doSomethingChild() // 21
我担心使用OLOO可能会有更优雅的方式。我主要担心的是,我认为我应该在儿童工厂做let parent = Object.create(Parent)
,但如果我这样做,那就不行了。
所以,1)我错过了什么,2)可以重构吗?
答案 0 :(得分:6)
你绝对应该比单一祖先类继承更喜欢组合(包括mixins),所以你就是在正确的轨道上。也就是说,JavaScript并不具备私有属性,因为您可能从其他语言中了解它们。我们在JS中使用闭包来保护数据隐私。
对于具有真实数据隐私的可组合原型(通过闭包),您正在寻找的是功能混合,它们是获取对象并返回添加了新功能的对象的函数。
但是,在我看来,使用可组合工厂(例如stamps)进行功能继承通常是更好的做法。 AFAIK,Stampit是可组合工厂中使用最广泛的实现。
标记是一个可组合的工厂函数,它根据描述符返回对象实例。邮票有一个名为.compose()
的方法。调用.compose()
方法时,使用当前标记作为基础创建新标记,由作为参数传递的可组合列表组成:
const combinedStamp = baseStamp.compose(composable1, composable2, composable3);
可组合是一个标记或POJO(普通旧JavaScript对象)标记描述符。
.compose()
方法兼作图章的描述符。换句话说,描述符属性附加到戳.compose()
方法,例如, stamp.compose.methods
。
可组合描述符(或仅描述符)是一个元数据对象,它包含创建对象实例所需的信息。 描述符包含:
methods
- 将添加到对象委托原型的一组方法。properties
- 将通过赋值添加到新对象实例的一组属性。initializers
- 将按顺序运行的一系列函数。邮票详细信息和参数将传递给初始化程序。staticProperties
- 将通过赋值给图章复制的一组静态属性。诸如“如何继承特权方法和私有数据?”和“继承层次结构有哪些好的替代方案?”等基本问题是许多JavaScript用户的难题。
让我们使用init()
库中的compose()
和stamp-utils
同时回答这两个问题。
compose(…composables: [...Composable]) => Stamp
接受任意数量的可组合并返回一个新标记。init(…functions: [...Function]) => Stamp
接受任意数量的初始化函数并返回一个新标记。首先,我们将使用闭包来创建数据隐私:
const a = init(function () {
const a = 'a';
Object.assign(this, {
getA () {
return a;
}
});
});
console.log(typeof a()); // 'object'
console.log(a().getA()); // 'a'
它使用函数范围来封装私有数据。请注意,必须在函数内部定义getter才能访问闭包变量。
这是另一个:
const b = init(function () {
const a = 'b';
Object.assign(this, {
getB () {
return a;
}
});
});
那些a
不是拼写错误。关键是要证明a
和b
的私有变量不会发生冲突。
但这是真正的享受:
const c = compose(a, b);
const foo = c();
console.log(foo.getA()); // 'a'
console.log(foo.getB()); // 'b'
WAT?是啊。您只是同时从两个来源继承了特权方法和私有数据。
使用可组合对象时,您应该遵守一些经验法则:
myNewObject
需要featureA
,featureB
和featureC
,因此:myNewFactory = compose(featureA, featureB, featureC); myNewObject = myNewFactory()
。请注意,myNewObject
不是featureA
,featureB
等的实例 ...而是实现,使用,或包含这些功能。如果你坚持这些准则,你的邮票和& mixins将不受常见遗传问题的影响,例如脆弱的基类问题,大猩猩/香蕉问题,必要性问题的重复等等......
答案 1 :(得分:1)
使用ES6课程,就像
一样简单class Parent {
constructor() {
this.publicVar = 1;
this._privateVar = 2;
}
getPrivate() {
return this._privateVar;
}
doSomething() {
return externalMethod(this.publicVar) + 10
}
}
class Child extends Parent {
constructor() {
super();
this._privateVar = 4;
}
doSomethingChild() {
return this.publicVar + 20
}
}
module.exports = {
parent: new Parent(),
child: new Child()
}
根据publicVar
和_privateVar
的角色,它们可能是静态属性。
使用_privateVar
财产并非偶然。通常_
命名约定(可能是非可枚举的描述符)足以将成员指定为私有/受保护。
Object.assign
作为ES6中的主要继承技术无效,但可以另外用于实现多态继承。
答案 2 :(得分:1)
在仔细阅读了所有帖子并试图理解OP的问题后,我认为OP的方法已经非常接近一个可靠的解决方案。毕竟,JS中的可靠封装主要是回归到一些基于闭包的技术。纯粹的对象和基于工厂的方法也很精简。
为了完全理解所提供的示例,我做了重构,更明确地命名了不同的可组合部分,尤其是“行为”部分。我也对孩子与父母的关系以及如何出口工厂感到不舒服。但由于这些例子大多是简化代码,因此必须经常猜测OP的现实世界问题。
这就是运行重构的代码,它确实保留了OP的方法,看起来像......
// an external method
function externalPassThroughMethod(value) {
return value;
}
// some composable object based behavior
const withGetPublicValueIncrementedByTen = {
getPublicValueIncrementedByTen() {
return (externalPassThroughMethod(this.publicValue) + 10);
}
};
// another composable object based behavior
const withGetPublicValueIncrementedByTwenty = {
getPublicValueIncrementedByTwenty() {
return (externalPassThroughMethod(this.publicValue) + 20);
}
};
// the parent factory
function createParent(publicOptions = {}) {
var localValue = 2;
// `publicValue` via `publicOptions`
return Object.assign({}, publicOptions, withGetPublicValueIncrementedByTen, {
getLocalValue() {
return localValue;
}
});
}
// the child factory
function createChild(parent) {
var localValue = 4;
// `publicValue` via `parent`
return Object.assign({}, parent, withGetPublicValueIncrementedByTwenty, {
getLocalValue() {
return localValue;
},
getLocalValueOfParent() { // object linking other object ...
return parent.getLocalValue(); // ... by forwarding.
}
});
}
// // Node export
// module.exports = {
// createParent: createParent,
// createChild : createChild
// }
// some (initial) key value pair
const initalPublicValue = { publicValue: 1 };
const parent = createParent(initalPublicValue);
const child = createChild(parent);
console.log('parent.getLocalValue()', parent.getLocalValue()); // 2
console.log('parent.getPublicValueIncrementedByTen()', parent.getPublicValueIncrementedByTen()); // 11
console.log('parent.getPublicValueIncrementedByTwenty', parent.getPublicValueIncrementedByTwenty); // [UndefinedValue]
console.log('child.getLocalValue()', child.getLocalValue()); // 4
console.log('child.getLocalValueOfParent()', child.getLocalValueOfParent()); // 2
console.log('child.getPublicValueIncrementedByTen()', child.getPublicValueIncrementedByTen()); // 11
console.log('child.getPublicValueIncrementedByTwenty', child.getPublicValueIncrementedByTwenty()); // 21
.as-console-wrapper { max-height: 100%!important; top: 0; }
下一个给定的示例代码采用刚刚提供的重构示例,但使用基于函数而不是基于对象的mixins和工厂,它们创建基于类的类型而不是基于普通对象(文字)的类型。然而,两个例子都有关于如何处理封装和组合的一个相同的方法......
// an external method
function externalPassThroughMethod(value) {
return value;
}
// some composable function based behavior
const withGetPublicValueIncrementedByTen = (function () {
function getPublicValueIncrementedByTen() {
// implemented once ...
return (externalPassThroughMethod(this.publicValue) + 10);
}
return function () {
// ... shared (same implementation) code.
this.getPublicValueIncrementedByTen = getPublicValueIncrementedByTen;
};
}());
// another composable function based behavior
const withGetPublicValueIncrementedByTwenty = (function () {
function getPublicValueIncrementedByTwenty() {
// implemented once ...
return (externalPassThroughMethod(this.publicValue) + 20);
}
return function () {
// ... shared (same implementation) code.
this.getPublicValueIncrementedByTwenty = getPublicValueIncrementedByTwenty;
};
}());
class Parent {
constructor(publicOptions = {}) {
function getLocalValue() {
return localValue;
}
var localValue = 2;
// `publicValue` via `publicOptions`
Object.assign(this, publicOptions);
withGetPublicValueIncrementedByTen.call(this);
this.getLocalValue = getLocalValue;
}
}
class Child {
constructor(parent) {
function getLocalValue() {
return localValue;
}
function getLocalValueOfParent() { // object linking other object ...
return parent.getLocalValue(); // ... by forwarding.
}
var localValue = 4;
// `publicValue` via `parent`
Object.assign(this, parent);
withGetPublicValueIncrementedByTwenty.call(this);
this.getLocalValue = getLocalValue;
this.getLocalValueOfParent = getLocalValueOfParent;
}
}
function createParent(publicOptions = {}) {
return (new Parent(publicOptions));
}
function createChild(parent) {
return (new Child(parent));
}
// // Node export
// module.exports = {
// createParent: createParent,
// createChild : createChild
// }
// some (initial) key value pair
const initalPublicValue = { publicValue: 1 };
const parent = createParent(initalPublicValue);
const child = createChild(parent);
console.log('parent.getLocalValue()', parent.getLocalValue()); // 2
console.log('parent.getPublicValueIncrementedByTen()', parent.getPublicValueIncrementedByTen()); // 11
console.log('parent.getPublicValueIncrementedByTwenty', parent.getPublicValueIncrementedByTwenty); // [UndefinedValue]
console.log('child.getLocalValue()', child.getLocalValue()); // 4
console.log('child.getLocalValueOfParent()', child.getLocalValueOfParent()); // 2
console.log('child.getPublicValueIncrementedByTen()', child.getPublicValueIncrementedByTen()); // 11
console.log('child.getPublicValueIncrementedByTwenty', child.getPublicValueIncrementedByTwenty()); // 21
.as-console-wrapper { max-height: 100%!important; top: 0; }
答案 3 :(得分:-1)
为了完成起见,使用Eric Elliott的答案变得非常简单:
var stampit = require('stampit')
function externalMethod(myVar) {
return myVar
}
parent = stampit().init(function({value}){
this.privateVar = 2
}).props({
publicVar: 1
}).methods({
doSomething() {
return externalMethod(this.publicVar) + 10
},
getPrivate() {
return this.privateVar
}
})
child = parent.init(function({value}){
this.privateVar = 4
}).methods({
doSomethingChild() {
return this.publicVar + 20
}
})
parent().getPrivate() // 2
parent().doSomething() // 11
child().getPrivate() // 4
child().doSomething() // 11
child().doSomethingChild() // 21