For the KISS principle, you can opt for the factory function pattern (aka closures) instead of use classes.
// Typings definition (stripped out at complie time):
type MyObject = {
readonly aPublicMethod: () => void;
readonly anotherPublicMethod: (anArgument: any) => any;
};
type MyObjectFactory = (aDependencyInstance: any, anotherDependencyInstance: any) => MyObject;
// Actual implementation:
const createMyObject: MyObjectFactory = (aDependencyInstance, anotherDependencyInstance) => {
// All "private" logic here, not visible outside
// Think it as the constructor or the class context
const aPrivateMethod = () => {
return aDependencyInstance; // Do stuff here
};
// Public methods are those returned
return {
aPublicMethod: () => {
return aPrivateMethod();
},
anotherPublicMethod: (anArgument) => {
return anotherDependencyInstance[anArgument]; // Do stuff here
}
}
}
// Usage:
// Dependencies instances
const firstDependencyInstance = {};
const secondDependencyInstance = [];
// Instantiate the object, think it like "new MyObject(...)"
const myObj = createMyObject(firstDependencyInstance, secondDependencyInstance);
myObj.aPublicMethod();
myObj.anotherPublicMethod('myArgument');
- define one factory per file with typings definition at the beginning of it to know right away what's inside;
- postpone
Factory
to factory's typing declaration name and prependcreate
to method's implementation name; - inject all dependencies needed to have more "portable" code with arguably no side effects;
- return only methods you want to be public: declare them as
readonly
to prevent "change of implementation" errors at compile-time and, if it's required to grant object immutability at runtime, wrap return statement withObject.freeze({ ... })
(beware is not a deep freeze but just at "root level").
- less code is produced by transpiling to ES5 (functions are "native", classes require polyfills ...to functions);
- make internal things really private: classes private fields still require polyfills and will be public once the code is transpiled;
- easier to define typings and group them for better readability;
- avoid
this
context workarounds (eg.bind
,that = this
); - may be easier to test;
- promotes composition over inheritance:
extends
means coupling things up, something you should do consciously because can bring to mutation, side effects, responsibility overload; - memory wise, functions are added in the
prototype
each time are created, so may be better to use Class when creating thousands of the same object because just one instance will be in theprototype
(if the target is ES6+); - not convinced yet? 1, 2, 3, 4