Fun with Stamps. Episode 19. Java/C# abstract methods in JavaScript

Vasyl Boroviak
3 min readNov 5, 2017

Hello. I’m developer Vasyl Boroviak and welcome to the nineteenth episode of Vasyl Boroviak presents Fun with Stamps.

TL;DR

The new @stamp/required stamp (aka behaviour) which checks if a certain method/property/staticProperty/configuration/etc exists. Otherwise, it will throw something like “Required: There must be myMethodName in this stamp methods” while creating an object instance from your stamp.

Why having this feature at all?

In Java/C# world people tend to think in terms of interfaces and their implementations. In JavaScript world it is rarely needed. Although, some object composition approaches (like traits.js) do have “abstract methods” feature.

On the other hand, what if your stamp have sense only if it was provided a property? In this case people typically create an initializer, which checks a property existence and then throws:

import stampit from '@stamp/it';const CheckMyPropertyExists = stampit({
init() {
if (!this.myProperty) throw new Error('myProperty is missing!');
}
});

To simplify Java/C# developers transition to stamps, and to remove that if-throw boilerplate code, there is the Required stamp — https://www.npmjs.com/package/@stamp/required

Converting Java/C# abstract methods to stamps

Java/C# code:

abstract class Dog {                   
abstract void jump(); // Insist jump() implemented by descendants
}

This code will throw at compilation time:

const dogInstance = new Dog();

Stamps:

import Required from '@stamp/required'; // Importing the stamplet Dog = Required.required({
methods: {
jump: Required // Insist jump() exists while creating an object
}
});

This code will throw at runtime (there is no compilation time in JS):

const dogInstance = Dog();
// Throws - “Required: There must be jump in this stamp methods”

But if we add the jump() method it will be ok:

Dog = Dog.methods({
jump() { ... }
});
const dogInstance = Dog(); // all good

And, of course, the jump() method will be attached to the instance prototype:

const proto = Object.getPrototypeOf(dogInstance);
console.log(proto.jump); // "function"

Stamps have moar powers than outdated Java/C#

See code examples. Read comments!

Insist a property exists:

const RequiresOwner = Required.required({
properties: {
owner: Required // Insist .owner exists
}
});
let Cat = stampit({ ... }).compose(RequiresOwner);
Cat(); // Throws!
Cat = Cat.properties({ owner: 'Vasyl' });
const cat = Cat(); // ok

Insist a static property exists:

const RequiresReadFromDatabaseFactory = Required.required({
staticProperties: {
readFromDatabase: Required // Insist Cat.readFromDatabase exists
}
});
let Cat = stampit({ ... }).compose(RequiresReadFromDatabaseFactory);
Cat(); // Throws!
Cat = Cat.statics({
async readFromDatabase(id) {
return await someCatDatabase.get(id)
}
});
const cat = Cat(); // ok

Insist you gave your stamp a runtime name:

const MustHaveBeenNamed = Required.required({
staticPropertyDescriptors: {
name: Required // Insist stamp have a non default runtime name
}
});
let Cat = stampit({ ... }).compose(MustHaveBeenNamed);
console.log(Cat.name); // "Stamp" <- the default name
Cat(); // Throws!
import {setName} from '@stamp/named'; // name helper stampCat = setName('Cat').compose(Cat); // just another way to compose :)
console.log(Cat.name); // "Cat" <- the new name
const
cat = Cat(); // ok

Have fun with stamps!

--

--