2 minutes
JavaScript (sort of) abstract class
I’ve never been a big JavaScript classes user. Never had to work much with “raw” prototypal inheritance either, to be honest, since most of what I do is Node.js so, modules and functions.
While I generally avoid pure object oriented programming in JavaScript, some patterns do come in handy, like abstract classes.
Of course, JavaScript has no such concept so some improvisation is required (yes yes, I know about TypeScript…).
In the code block bellow, EmailProvider
is our abstract class. It has a constructor and a “fake” abstract method.
The idea is to use the constructor of the abstract class to perform runtime validations regarding implemented methods and proper instantiation.
class EmailProvider {
#config = {}
constructor(config) {
this.#config = config
if (new.target === EmailProvider) {
throw new Error('Cannot construct EmailProvider instances directly')
}
if (this.send === EmailProvider.prototype.send) {
throw new Error(`EmailProvider[${new.target.name}]: missing send implementation`)
}
}
send() {
throw new Error('EmailProvider.send should not be called directly')
}
}
Breakdown
Check if the class is being instantiated via parent or child constructor. new.target
refers to the constructor that was directly invoked by new (see MDN docs).
if (new.target === EmailProvider) {
throw new Error('Cannot construct EmailProvider instances directly')
}
Check if this.send
is the same as the one defined in the abstract class prototype. If it is, it means the method was not implemented (so, it’s sort of abstract).
This is just a way to try and enforce the implementation of methods in child classes.
if (this.send === EmailProvider.prototype.send) {
throw new Error(`EmailProvider[${new.target.name}]: missing send implementation`)
}
Of course, this is optional and quite rough. If you’re making use of this, make sure you adapt to your application’s needs. Also, I have not tried this with more than one level of inheritance (and would not try either).
Example
class EmailProvider {...}
class Sendgrid extends EmailProvider {
send(message) {
console.log('swoosh: ', message)
}
}
class Mailjet extends EmailProvider {}
const providers = {
SENDGRID: 'sendgrid',
MAILJET: 'mailjet',
}
const MailerFactory = (provider, config = {}) => {
switch (provider) {
case providers.SENDGRID: return new Sendgrid(config)
case providers.MAILJET: return new Mailjet(config)
default: throw new Error('Email provider not implemented')
}
}
MailerFactory(providers.SENDGRID) // ok
MailerFactory(providers.MAILJET) // throws
new EmailProvider() // throws
371 Words
2020-11-09