Sharing state when applying Douglas Crockford's composition pattern - javascript

This is the form of constructor which Douglas Crockford suggests in his book "How Javascript works" and in his lectures.
const constructor_x = function (spec) {
let { a } = spec // private state
// methods can modify private state
const method_x = function () { a = '...' }
// methods are exposed as public interface
return Object.freeze({ method_x })
}
He suggests the following pattern for composition:
const constructor_y = function (spec) {
let { b } = spec // private state
// we can call other constructor and borrow functionality
const { method_x } = constructor_x(spec)
// we define new methods
const method_y = function () { b = '...' }
// we can merge borrowed and new functionality
// and expose everything as public interface
return Object.freeze({ method_x, method_y })
}
So here we see how to compose constructor_x and constructor_y. But my problem with this example (and all examples used when this pattern is presented) is that constructor_x and constructor_y make separate private states. constructor_x works on variable a, while constructor_y works on variable b. What if we want our constructors to share state? What if constructor_y also wants to work with variable a?
const constructor_y = function (spec) {
let { a, b } = spec
const { method_x } = constructor_x(spec)
const method_y = function () { b = '...' }
const method_z = function () {
// we may want to read `a` and maybe write to it
a = '...'
}
return Object.freeze({ method_x, method_y, method_z })
}
Of course this doesn't achieve what I want because a which constructor_y sees is not the same a constructor_x sees. If I used this, I could have achieved that maybe like so:
const constructor_x = function (spec) {
return {
_a: spec.a,
method_x () { this._a = '...' }
}
}
const constructor_y = function (spec) {
return {
...constructor_x(spec),
_b: spec.b
method_y () { this._b = '...' },
method_z () { this._a = '...' }
}
}
But here I have lost privacy of variables _a and _b since they are attached to instance and are accessible just like methods. The best I can do is add underscore prefix which Douglas Crockford calls a sign of incompetence. I also lost instance's rigidness because it can no longer be frozen.
I could have exposed accessors for variable a in constructor_x like so:
const constructor_x = function (spec) {
let { a } = spec // private state
// methods can modify private state
const method_x = function () { a = '...' }
// methods are exposed as public interface
return Object.freeze({
method_x,
get_a () { return a },
set_a (val) { a = val }
})
}
const constructor_y = function (spec) {
let { a, b } = spec
const { method_x, get_a, set_a } = constructor_x(spec)
const method_y = function () { b = '...' }
const method_z = function () { set_a('...') }
return Object.freeze({ method_x, method_y, method_z })
}
These accessors can now be used by constructor_y to access private state of constructor_x. They are something like protected members in classical inheritance model. This makes constructor_x in some way special: It is not to be used as normal constructor, but only for composition inside other constructors. Another problem is that if we had another constructor like constructor_x which works on private variable a, we couldn't use them together in composition:
// another constructors which wants to work on `a`
const constructor_x2 = function (spec) => {
let { a } = spec
const method_z = function () { a = '...' }
return Object.freeze({
method_z,
get_a () { return a },
set_a (val) { a = val }
})
}
const constructor_y = function (spec) {
let { a, b } = spec
const { method_x, get_a, set_a } = constructor_x(spec)
const { method_x2, get_a: get_a2, set_a: set_a2 } = constructor_x2(spec)
// How do I use variable a now? There are two of them
// and constructors x and x2 don't share them.
}
All of this would not be a problem if I used this and modified state on the instance.

From my comments above ...
"1/2 ... First, all this creator functions should be referred to as factories or factory functions. They are not constructors. ... What if we want our constructors to share state? " ... then just implement the factories in a way that they can share each their entire inner/encapsulated state object and/or that they aggregate a shared state object while running the object creation process (the chained invocation of related functions during the composition process)."
What the OP wants to achieve can not be entirely covered by closure creating factory functionality according to Crockford / provided by the OP.
Encapsulated but shared (thus also mutable) state amongst "Function based Composable Units of Reuse" gets achieved best by a single factory which takes care of the composition process by invoking one or more mixin like functions which in addition to the to be shaped / aggregated type (the latter should carry public methods only) also need to get passed the type's local state(which will be accessed by the types's public methods).
function withActionControl(type, actionState) {
actionState.isInAction = false;
return Object.assign(type, {
monitorActions() {
const {
isInAction,
...otherActions } = actionState;
return { ...otherActions };
},
});
}
function withCanSwimIfNotBlocked(type, state) {
state.isSwimming = false;
return Object.assign(type, {
startSwimming() {
if (!state.isInAction) {
state.isInAction = true;
state.isSwimming = true;
console.log({ startSwimming: { state } })
}
},
stopSwimming() {
if (state.isSwimming) {
state.isInAction = false;
state.isSwimming = false;
console.log({ stopSwimming: { state } })
}
},
});
}
function withCanFlyIfNotBlocked(type, state) {
state.isFlying = false;
return Object.assign(type, {
startFlying() {
if (!state.isInAction) {
state.isInAction = true;
state.isFlying = true;
console.log({ startFlying: { state } })
}
},
stopFlying() {
if (state.isFlying) {
state.isInAction = false;
state.isFlying = false;
console.log({ stopFlying: { state } })
}
},
});
}
function withLaysEggsIfNotBlocked(type, state) {
state.isLayingEggs = false;
return Object.assign(type, {
startLayingEggs() {
if (!state.isInAction) {
state.isInAction = true;
state.isLayingEggs = true;
console.log({ startLayingEggs: { state } })
}
},
stopLayingEggs() {
if (state.isLayingEggs) {
state.isInAction = false;
state.isLayingEggs = false;
console.log({ stopLayingEggs: { state } })
}
},
});
}
function createSeabird(type) {
const birdState = {
type,
actions: {},
};
const birdType = {
valueOf() {
return JSON.parse(
JSON.stringify(birdState)
);
},
};
const { actions } = birdState;
withActionControl(birdType, actions)
withLaysEggsIfNotBlocked(birdType, actions);
withCanFlyIfNotBlocked(birdType, actions);
withCanSwimIfNotBlocked(birdType, actions);
return birdType;
}
const wisdom = createSeabird({
family: 'Albatross',
genus: 'North Pacific albatross',
species: 'Laysan albatross',
name: 'Wisdom',
sex: 'female',
age: 70,
});
console.log({ wisdom });
console.log('wisdom.valueOf() ...', wisdom.valueOf());
console.log('wisdom.monitorActions() ...', wisdom.monitorActions());
console.log('wisdom.startFlying();')
wisdom.startFlying();
console.log('wisdom.startFlying();')
wisdom.startFlying();
console.log('wisdom.startSwimming();')
wisdom.startSwimming();
console.log('wisdom.startLayingEggs();')
wisdom.startLayingEggs();
console.log('wisdom.stopFlying();')
wisdom.stopFlying();
console.log('wisdom.stopFlying();')
wisdom.stopFlying();
console.log('wisdom.startSwimming();')
wisdom.startSwimming();
console.log('wisdom.startSwimming();')
wisdom.startSwimming();
console.log('wisdom.startLayingEggs();')
wisdom.startLayingEggs();
console.log('wisdom.startFlying();')
wisdom.startFlying();
console.log('wisdom.stopSwimming();')
wisdom.stopSwimming();
console.log('wisdom.stopSwimming();')
wisdom.stopSwimming();
console.log('wisdom.startLayingEggs();')
wisdom.startLayingEggs();
console.log('wisdom.startLayingEggs();')
wisdom.startLayingEggs();
console.log('wisdom.valueOf() ...', wisdom.valueOf());
console.log('wisdom.monitorActions() ...', wisdom.monitorActions());
.as-console-wrapper { min-height: 100%!important; top: 0; }
Close with one of the above initial comments ...
"2/2 ... Just take advantage of the language's flexibility and expressiveness. Just be aware of the advantages, pitfalls and comprehensibility (to others) of your modeling approach(es). And once this is checked don't worry about [too strict]* Crockford disciples (or any other school / religion / cult). A good teacher shows you a [path]* and allows / encourages you to discover or follow your own, once you understood what the base/basics are good for."

Related

JS advantages of module pattern?

I know it's a duplicate but I don't understand the other posts: I'm doing an exercise on freeCodeCamp that I don't understand, it's about modules: What's the advantage of doing this:
const motionModule = (function() {
return {
isCuteMixin: function(obj) {
obj.isCute = function() {
return true;
};
},
singMixin: function(obj) {
obj.sing = function() {
console.log("Singing to an awesome tune");
};
}
};
})();
instead of this:
const motionModule = {
isCuteMixin: function(obj) {
obj.isCute = function() {
return true;
};
},
singMixin: function(obj) {
obj.sing = function() {
console.log("Singing to an awesome tune");
};
}
};
One advantage is you can emulate private variables and methods which are not accessible from outside the returned object. This helps keeping the data and functionality together and avoids corruption of global namespace.
const motionModule = (function() {
let song = 'My song'; // private variable
function singTheSong() {
// private method
}
return {
isCuteMixin: function(obj) {
obj.isCute = function() {
return true;
};
},
singMixin: function(obj) {
obj.sing = function() {
console.log("Singing to an awesome tune" + song);
singTheSong();
};
}
};
})();
// cannot call singTheSong from here
Within a module, you would often want the methods to be able to access each other and shared variables. To do this in your 2nd example, you need to attach them to the object and access them via the this keyword, and also (within the mixin creating functions) use arrow functions to ensure this refers to the right object.
const motionModule = {
song: "La La La",
sing: function() {
console.log(this.song);
},
singMixin: function(obj) {
obj.sing = () => {
console.log(`Singing ${this.song}`);
};
}
};
const a = {};
motionModule.sing();
motionModule.singMixin(a);
a.sing();
Modern ES6 class declarations also require you to work in this way.
class MotionModule {
song = "La La La";
sing() {
console.log(this.song);
}
singMixin(obj) {
obj.sing = () => {
console.log(`Singing ${this.song}`);
};
}
}
const motionModule = new MotionModule();
motionModule.sing();
const a = {};
motionModule.singMixin(a);
a.sing();
As shown in another answer, the first example (an immediately invoked function expression) allows you to access other variables and methods defined within the module without using this, and gives you greater control over which methods and variables are accessible from outside the module.

Need a custom assignment implementaion

I am working with some state management application where I have a data structure as follows
const mainObject = {
firstLevel: {
secondLevel: {
thirdLevel: {
actualProperty: 'Secret'
}
}
},
firstLevelUntouched:{
secondLevelUntouched:{
thirdLevelUntouched:{
untouchedProperty:'I don`t want to change'
}
}
}
};
I want to change the actualProperty to a new value which out a deepClone
I did it with the following code
const modified = {
...mainObject,
...{
firstLevel: {
...mainObject.firstLevel,
...{
secondLevel: {
...mainObject.firstLevel.secondLevel,
thirdLevel: {
...mainObject.firstLevel.secondLevel.thirdLevel,
actualProperty: 'New secret'
}
}
}
}
}
}
But its looks like Bulky Code. So I need to write a function like
modified = myCustomAssignment(mainObject, ['firstLevel', 'secondLevel', 'thirdLevel', 'actualProperty'], 'New secret')
Can anyone help me on this?
You could use a simple traversal function for this that just traverses the passed properties until it arrives as the final one, then sets that to the new value.
function myCustomAssignment(mainObject, propertyList, newValue) {
const lastProp = propertyList.pop();
const propertyTree = propertyList.reduce((obj, prop) => obj[prop], mainObject);
propertyTree[lastProp] = newValue;
}
You could even add propertyList = propertyList.split('.') to the top of this function so the list can be passed in as an easy-to-read string, like myCustomAssignment(mainObject, 'firstLevel.secondLevel.thirdLevel.actualProperty', 'new value') if you wanted that.
export function mutateState(mainObject: object, propertyList: string[], newValue: any) {
const lastProp = propertyList.pop();
const newState: object = { ...mainObject };
const propertyTree =
propertyList
.reduce((obj, prop) => {
obj[prop] = { ...newState[prop], ...obj[prop] };
return obj[prop];
}, newState);
propertyTree[lastProp] = newValue;
return newState as unknown;
}
This fixed my issue. thanks all..

Transforming an object with a javascript class

Iā€™m transforming data I receive from an API. The frontend requires some calculations to be displayed.
What is the proper way to handle the data transformation?
Should I be defining a property to the object being passed? If so, why
Is this a good use case to use setters and getters or would that be unnecessary?
const dogData = {
dog_name: "filo",
born_time: 1530983852,
coat_color: "brown"
};
class Dog {
constructor(data) {
//do I need to set this.dog to the data object, what's the benefit of doing so?
this.dog = data;
this.name = this.dog.dog_name;
// vs
this.name = data.dog_name;
//Should I use setters and getters?
this.color = this.dog.coat_color;
// vs
this._color = this.dog.coat_color;
this.age = this.calculateAge();
}
calculateAge() {
return Date.now().getTime() - this.dog.born_time;
}
//Is this a good case where I should using getters to access the properties or would that be superfluous?
//should I be setting the properties with setters in this case?
get color() {
return this._color;
}
}
const dog = new Dog(dogData)
Your don't need to make a copy of data into your class.
You can assign the class fields directly (using object destructuring to be more readable).
const data = {
dog_name: 'filo',
born_time: 1530983852,
coat_color: 'brown'
}
class Dog {
// Directly assign values
constructor({ dog_name, born_time, coat_color }) {
this.name = dog_name
this.bornAt = born_time
this.color = coat_color
}
// Getter for computed properties
get age() {
return Date.now() - this.bornAt
}
}
const dog = new Dog(data)
Getters are needed only for computed property (dynamic or formatted values).
Good exemple:
class Person {
constructor({ firstname, lastname }) {
this.firstname = firstname
this.lastname = lastname
}
get fullname() {
return `${this.firstname} ${this.lastname}`
}
}
class Dog {
constructor(data) {
const {
dog_name: name,
born_time: age,
coat_color: color
} = data;
Object.assign(this, {
name,
age,
color
});
}
}
const dogData = {
dog_name: "filo",
born_time: 1530983852,
coat_color: "brown"
};
const dog = new Dog(dogData);
console.log(dog.name);
Q:
Shall I nevertheless throw in a possible read only approach? ā€“ Peter Seliger
A:
It wouldn't hurt. I appreciate the different approaches. ā€“ Matthew Moran
... here we go ...
// module start ... e.g. file: "Dog.js"
// locally scoped helper function
function calculateAge(dateOfBirth) {
return (Date.now() - dateOfBirth);
}
/*export default */class Dog {
constructor(initialValue) {
Object.defineProperties(this, {
valueOf: { // just in order to hint what `initialValue` might still be good for.
value: function () {
return Object.assign({}, initialValue);
}
},
name: {
value: initialValue.dog_name,
enumerable: true
},
color: {
value: initialValue.coat_color,
enumerable: true
},
age: {
get() {
return calculateAge(initialValue.born_time);
},
enumerable: true,
}
});
}
}
// module end.
// test
const dogData = {
dog_name: "filo",
born_time: 1530983852,
coat_color: "brown"
};
const dog = new Dog(dogData);
console.log('Object.keys(dog) : ', Object.keys(dog));
console.log('dog.valueOf() : ', dog.valueOf());
console.log('dog.age : ', dog.age);
console.log('dog.name : ', dog.name);
console.log('dog.color : ', dog.color);
console.log('(dog.age = 298146912) : ', (dog.age = 298146912) && dog.age);
console.log('(dog.name = "spot") : ', (dog.name = "spot") && dog.name);
console.log('(dog.color = "black") : ', (dog.color = "black") && dog.color);
.as-console-wrapper { max-height: 100%!important; top: 0; }

ES6 dynamic destructuring

I need to apply different destructuring for the function response depending of global flag [single service for multiple apps]
// Destructuring template should be defined as value
let destructuringTemplate;
if (flag) {
destructuringTemplate = {data: {classA: user}};
} else {
destructuringTemplate = {data: {classB: user}};
}
// This technique would not work, this is just my idea representation.
this.getUser(({destructuringTemplate: user) => { this.localUser = user });
At this moment it works this way:
let destructuringTemplate;
if (flag) {
destructuringTemplate = ({data: {classA: user}}) => user;
} else {
destructuringTemplate = ({data: {classB: user}}) => user;
}
this.getUser(response => { this.localUser = destructuringTemplate(response)};
it is kinda ugly, some suggestion how it should be done?
You could use a computed property name with a conditional (ternary) operator ?:.
var flag = true,
object = { data: { classA: 'foo', classB: 'bar' } },
{ data: { [flag ? 'classA' : 'classB']: user } } = object,
{ data: { [!flag ? 'classA' : 'classB']: user1 } } = object;
console.log(user);
console.log(user1);
You don't need to use destructuring, use simple dot/bracket notation:
const userClass = flag ? 'classA' : 'classB'
this.getUser(response => { this.localUser = response.data[userClass] })
If you want to reuse this logic, just create simple function, e.g.:
const extractUser = response => response.data[userClass]

Unsubscribe from Redux store when condition is true?

I'm employing the suggestion from #gaearon to setup a listener on my redux store. I'm using this format:
function observeStore(store, select, onChange) {
let currentState;
if (!Function.prototype.isPrototypeOf(select)) {
select = (state) => state;
}
function handleChange() {
let nextState = select(store.getState());
if (nextState !== currentState) {
currentState = nextState;
onChange(currentState);
}
}
let unsubscribe = store.subscribe(handleChange);
handleChange();
return unsubscribe;
}
I'm using this in an onEnter handler for a react-router route:
Entity.onEnter = function makeFetchEntity(store) {
return function fetchEntity(nextState, replace, callback) {
const disposeRouteHandler = observeStore(store, null, (state) => {
const conditions = [
isLoaded(state.thing1),
isLoaded(state.thing2),
isLoaded(state.thing3),
];
if (conditions.every((test) => !!test) {
callback(); // allow react-router to complete routing
// I'm done: how do I dispose the store subscription???
}
});
store.dispatch(
entities.getOrCreate({
entitiesState: store.getState().entities,
nextState,
})
);
};
};
Basically this helps gate the progression of the router while actions are finishing dispatching (async).
My problem is that I can't figure out where to call disposeRouteHandler(). If I call it right after the definition, my onChange function never gets a chance to do it's thing, and I can't put it inside the onChange function because it's not defined yet.
Appears to me to be a chicken-egg problem. Would really appreciate any help/guidance/insight.
How about:
Entity.onEnter = function makeFetchEntity(store) {
return function fetchEntity(nextState, replace, callback) {
let shouldDispose = false;
const disposeRouteHandler = observeStore(store, null, (state) => {
const conditions = [
isLoaded(state.thing1),
isLoaded(state.thing2),
isLoaded(state.thing3),
];
if (conditions.every((test) => !!test) {
callback(); // allow react-router to complete routing
if (disposeRouteHandler) {
disposeRouteHandler();
} else {
shouldDispose = true;
}
}
});
if (shouldDispose) {
disposeRouteHandler();
}
store.dispatch(
entities.getOrCreate({
entitiesState: store.getState().entities,
nextState,
})
);
};
};
Even though using the observable pattern leads to some buy-in, you can work around any difficulties with normal js code. Alternatively you can modify your observable to suit your needs better.
For instance:
function observeStore(store, select, onChange) {
let currentState, unsubscribe;
if (!Function.prototype.isPrototypeOf(select)) {
select = (state) => state;
}
function handleChange() {
let nextState = select(store.getState());
if (nextState !== currentState) {
currentState = nextState;
onChange(currentState, unsubscribe);
}
}
unsubscribe = store.subscribe(handleChange);
handleChange();
return unsubscribe;
}
and
Entity.onEnter = function makeFetchEntity(store) {
return function fetchEntity(nextState, replace, callback) {
const disposeRouteHandler = observeStore(store, null, (state, disposeRouteHandler) => {
const conditions = [
isLoaded(state.thing1),
isLoaded(state.thing2),
isLoaded(state.thing3),
];
if (conditions.every((test) => !!test) {
callback(); // allow react-router to complete routing
disposeRouteHandler();
}
}
store.dispatch(
entities.getOrCreate({
entitiesState: store.getState().entities,
nextState,
})
);
};
};
It does add a strange argument to onChange but it's just one of many ways to do it.
The core problem is that handleChange gets called synchronously immediately when nothing has changed yet and asynchronously later. It's known as Zalgo.
Inspired by the suggestion from #DDS, I came up with the following alteration to the other pattern mentioned in #gaearon's comment:
export function toObservable(store) {
return {
subscribe({ onNext }) {
let dispose = this.dispose = store.subscribe(() => {
onNext.bind(this)(store.getState())
});
onNext.bind(this)(store.getState());
return { dispose };
},
dispose: function() {},
}
}
This allows me to invoke like:
Entity.onEnter = function makeFetchEntity(store) {
return function fetchEntity(nextState, replace, callback) {
toObservable(store).subscribe({
onNext: function onNext(state) {
const conditions = [/* many conditions */];
if (conditions.every((test) => !!test) {
callback(); // allow react-router to complete routing
this.dispose(); // remove the store subscription
}
},
});
store.dispatch(/* action */);
};
};
The key difference is that I'm passing a regular function in for onNext so as not to interfere with my bind(this) in toObservable; I couldn't figure out how to force the binding to use the context I wanted.
This solution avoids
add[ing] a strange argument to onChange
... and in my opinion also conveys a bit more intent: this.dispose() is called from within onNext, so it kinda reads like onNext.dispose(), which is exactly what I want to do.

Categories