Fun with Stamps. Episode 13. Method collision control

Vasyl Boroviak
4 min readFeb 14, 2017

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()); // 1234
const 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 method
const 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!

--

--