Fun with Stamps. Episode 13. Method collision control
Hello. I’m developer Vasyl Boroviak and welcome to the thirteenth episode of Vasyl Boroviak presents Fun with Stamps.
TL;DR
You can forbid method overwrite (aka override, aka collision). Also, you can collect collided methods to a single method.
Protecting a method from overwrites / overrides / collisions
Very often people are asking:
How to avoid method overwrites/overrides/collisions using stamps?
A usual answer was:
Your stamp is too fat!
But blaming the code author instead of giving a solution is a bad approach. So, now you can setup your stamp and make sure none else overrides your method implementation.
Quick example:
import Collision from '@stamp/collision';const HasPassword = compose({
methods: {
getPassword() { ... },
setPassword(pwd) { ... }
}
})
.compose(Collision) // ADD BEHAVIOR (AKA STAMP)
.collisionSetup({ forbid: ['getPassword'] }); // SET IT UP
From now on, if anyone or anything would try to override the getPassword
then an exception will be thrown. For example:
const DatabaseClient = compose(
...,
HasPassword, // ADD STAMP FROM ABOVE
...
);DatabaseClient.setPassword('1234');
console.log(DatabaseClient.getPassword()); // 1234const HackedDbClient = DatabaseClient
.compose({ // THIS LINE THROWS AN EXCEPTION
methods: {
getPassword() { return 'try to hack database client'; }
}
});
In practice you might want a general purpose stamps (aka behaviors) like this:
const ForbidRedrawCollision =
Collision.collisionSetup({ forbid: ['redraw'] });
You can compose it with any other stamp to protect its redraw()
method from overwrites.
Protecting ALL methods form overwrites / overrides / collisions
Just use the collisionProtectAnyMethod()
static method.
const HasReadonlyPassword = Collision.compose({
methods: {
getPassword() { ... },
setPassword(pwd) { ... }
}
}).collisionProtectAnyMethod();
Done! No any method of this stamp, or any stamp it was composed with, can be overridden now.
Allowing overwrites / overrides / collisions of all methods
Sometimes, you might want to actually override a collision-protected method. E.g. in your unit tests. There is a special API for that — collisionSettingsReset()
.
In this example we will allow overrides of the HasReadonlyPassword
methods above.
let getPasswordInvoked = false;
const Mocked = HasReadonlyPassword
.collisionSettingsReset() // Allow overrides back again
.compose({ methods: {
getPassword() {
getPasswordInvoked = true;
}
}});
Mocked().setPassword('42'); // setPassword calls getPassword
console.log(getPasswordInvoked) // true
Deferring (or aggregating) collided methods
Another feature of the Collision
stamp is deferred methods. Meaning that if several deferred methods collide they will be aggregated to a single method which will wrap the methods array and call them one by one.
import Collision from '@stamp/collision';// General purpose behavior to defer "draw()" method collisions
const DeferDraw = Collision.collisionSetup({defer: ['draw']});
const Border = compose(DeferDraw, {
methods: {
draw() { /* implementation */ }
}
});
const Button = compose(DeferDraw, {
methods: {
draw() { /* implementation */ }
}
});const ModalDialog = compose(Border, Button);
const dialog = ModalDialog();// Call the "draw()" method of both Border and Button
dialog.draw();
Private method which is forbidden to override
From the Episode 12 you know that you can privatize properties and methods of a stamp.
import Privatize from '@stamp/privatize';
import compose from '@stamp/compose';const Password = compose({
properties: {
password: '12345qwert'
},
methods: {
getPassword() {
return this.password;
},
setPassword(pwd) {
this.password = pwd;
}
}
})
.compose(Privatize) // Privatizing all the properties by default
.privatizeMethods('getPassword'); // Privatize the methodconst obj = Password();
However, anyone can accidentally override your private methods!
Password = Password.compose({
methods: {
getPassword() { /* it's a mistake :-( */ },
setPassword() { /* it's a mistake :-( */ }
}
});
This is not private enough. In terms of Java/C# they are more like protected methods.
Good news though! You can combine both Collision
and Privatize
stamps to get really private methods. :)
import Collision from '@stamp/collision';Password = Password.compose(Collision).collisionSetup({
forbid: ['getPassword', 'setPassword']
});
Done! Accidental override of your private methods is nearly impossible now.
This code will throw now:
Password = Password.compose({ // THROWS!
methods: {
getPassword() { /* it's a mistake :-( */ },
setPassword() { /* it's a mistake :-( */ }
}
});
For more examples, see these unit tests.
Have fun with stamps!
- Episode 1. Stamp basics
- Episode 2. Dependency injection in FP
- Episode 3. Comparing with the ES2015 classes
- Episode 4. Implementing stamps yourself in 30 LOC
- Episode 5. Composition design pattern
- Episode 6. Statics — properties on stamps
- Episode 7. Early and late dependency injection
- Episode 8. Tracking and overriding composition
- Episode 9. Detaching compose()
- Episode 10. My stamp mental model
- Episode 11. Interfering composition
- Episode 12. New @stamp home
- Episode 13. Method collision control (this article)
- Episode 14. New @stamp/it as a replacement of Stampit
- Episode 15. The @stamp/ modules ecosystem
- Episode 16. TypeScript mix-in classes vs Stamps
- Episode 17. Easy 100% unit test coverage in JS
- Episode 18. Dependency injection paradise
- Episode 19. Java/C# abstract methods in JavaScript
- Episode 20. Stampit v4
- Episode 21. Private data in JavaScript. 4 ways using stamps
- Episode 22. JavaScript instanceof as composable stamp
- Episode 23. New stampit.js.org with all the docs
- Episode 24. New “name” feature