问题
Let's say I have a Thing
class which I want to be both Hideable
and Openable
.
Using a similar approach to Douglas Crockford's object creation through composition, I have been able to "inherit" from multiple classes.
This approach does not work with accessors (getter/setters).
I need to use classes as it's a requirement. I'm also finding that I am duplicating functionality from class to class, but I don't want these to inherit from a base class.
Any ideas?
The progress I have made so far is in the below snippet:
class Openable {
constructor(isOpen = false) {
this._isOpen = isOpen;
}
get isOpen() {
return this._isOpen + ' is stupid.';
}
set isOpen(value) {
this._isOpen = value;
}
}
class Hideable {
constructor(isHidden = false) {
this._isHidden = isHidden;
}
get isHidden() {
return this._isHidden + ' is stupid.';
}
set isHidden(value) {
this._isHidden = value;
}
}
class Thing {
constructor(config) {
let { isOpen, isHidden } = config;
let openable = new Openable(isOpen);
this.isOpen = openable.isOpen;
let hideable = new Hideable(isHidden);
this.isHidden = openable.isHidden;
}
}
let thing = new Thing({
isOpen: true,
isHidden: false
});
回答1:
Because isOpen
and isHidden
are accessors, you can't just grab a copy of them, you have to access them when you want them.
Still, you can create your own isOpen
, isHidden
which use the underlying ones:
let openable = new Openable(isOpen);
Object.defineProperty(this, "isOpen", {
get: () => openable.isOpen,
set: value => {
openable.isOpen = value;
}
});
let hideable = new Hideable(isHidden);
Object.defineProperty(this, "isHidden", {
get: () => hideable.isHidden,
set: value => {
hideable.isHidden = value;
}
});
Live example on Babel's REPL
Naturally, if you do this a lot, you'd want to have a worker function to set that up rather than retyping it all the time:
function wrapProperty(dest, src, name) {
Object.defineProperty(dest, name, {
get: () => src[name],
set: value => { src[name] = value; }
});
}
(or do it by grabbing the property descriptor and updating it)
then:
wrapProperty(this, openable, "isOpen");
wrapProperty(this, hideable, "isHidden");
I'd question the requirement that you must use class
for Openable
and Hideable
. They look much more like mixins to me.
回答2:
Besides that the OP's accessor approach via "pseudo private property" notation and prototypal getters/setters for Openable
/Hideable
classes already is questionable, trait
s would come closest to the also doubtable requirement of using classes as mixin
surrogates just for the sake of meeting documentation requirements.
As long as JavaScript does not provide traits natively, one has to stick to either more advanced class based mixin patterns ore one remembers Angus Croll's Flight Mixins.
A mixin's function
body one has to write is close enough to the constructor
body of a class
. Nevertheless function based mixins never will be instantiated but always have to be applied to an object/type via either call
or apply
.
A possible solution, featuring this kind of mixin approach, that already reliably fulfills the OP's requirements then might look like the next provided example code ...
let
Openable = (function openableMixinFactory () {
let
defineProperty = Object.defineProperty,
isBoolean = (type => (typeof type == 'boolean'));
return function openableMixinApplicator (isOpen = false) {
let
openableCompositeType = this,
getIsOpen = (() => isOpen),
setIsOpen = (value => ((isBoolean(value) && (isOpen = value)) || (void 0)));
defineProperty(openableCompositeType, 'isOpen', {
get: getIsOpen,
set: setIsOpen,
enumerable: true
});
return openableCompositeType;
};
}()),
Hideable = (function hideableMixinFactory () {
let
defineProperty = Object.defineProperty,
isBoolean = (type => (typeof type == 'boolean'));
return function hideableMixinApplicator (isHidden = false) {
let
hideableCompositeType = this,
//getIsHidden = (() => isHidden),
getIsHidden = (() => [isHidden, 'is stupid.'].join(' ')),
setIsHidden = (value => ((isBoolean(value) && (isHidden = value)) || (void 0)));
defineProperty(hideableCompositeType, 'isHidden', {
get: getIsHidden,
set: setIsHidden,
enumerable: true
});
return hideableCompositeType
};
}());
class Thing {
constructor(config) {
let
{isOpen, isHidden} = config;
Openable.call(this, isOpen);
Hideable.call(this, isHidden);
}
}
var
thing = new Thing({ isOpen: true/*, isHidden: false*/ });
console.log('thing : ', thing);
console.log('thing.isOpen : ', thing.isOpen);
console.log('thing.isHidden : ', thing.isHidden);
console.log('(thing.isOpen = "xyz") : ', (thing.isOpen = "abc"));
console.log('(thing.isHidden = "xyz") : ', (thing.isHidden = "xyz"));
console.log('thing.isOpen : ', thing.isOpen);
console.log('thing.isHidden : ', thing.isHidden);
console.log('(thing.isOpen = false) : ', (thing.isOpen = false));
console.log('(thing.isHidden = true) : ', (thing.isHidden = true));
console.log('thing.isOpen : ', thing.isOpen);
console.log('thing.isHidden : ', thing.isHidden);
.as-console-wrapper { max-height: 100%!important; top: 0; }
Other answers of mine at SO, that provide similar solutions to related questions, featuring the same approach are ...
- How do I organize data by common traits?
- Composition/Inheritance/Factory - Best Pattern For This Case
- Multiple inheritance using classes
- Class inheritance between projects
来源:https://stackoverflow.com/questions/35064677/accessors-composition-in-es6-classes