Javascript ES6 generic builder pattern for large hierarchy - javascript

I am trying to come up with a generic builder function for a large hierarchy of objects in Javascript. The goal is to implement as much functionality as possible at the top of the hierarchy. After playing around for a little while, I have ended up with a structure I like, although am not completely happy with.
The structure currently looks somewhat like this. I have attached a working (simplified) version below:
class AbstractItem {
constructor(build) {
if (this.constructor === AbstractItem) {
throw new TypeError("Oops! AbstractItem should not be instantiated!");
}
this._id = build.id;
}
/*
The generic ItemBuilder is part of an abstract superclass.
Every item should have an ID, thus the builder validates this.
It also provides a generic build() function, so it does not have to be re-implemented by every subclass.
*/
static get Builder() {
/*
This constant references the constructor of the class for which the Builder function was called.
So, if it was called for ConcreteItem, it will reference the constructor of ConcreteItem.
This allows us to define a generic build() function.
*/
const BuildTarget = this;
class ItemBuilder {
constructor(id) {
if (!id) {
throw new TypeError('An item should always have an id!');
}
this._id = id;
}
//The generic build method calls the constructor function stored in the BuildTarget variable and passes the builder to it.
build() {
return new BuildTarget(this);
}
get id() {
return this._id;
}
}
return ItemBuilder;
}
doSomething() {
throw new TypeError("Oops! doSomething() has not been implemented!");
}
get id() {
return this._id;
}
}
class AbstractSubItem extends AbstractItem {
constructor(build) {
super(build);
if (this.constructor === AbstractSubItem) {
throw new TypeError("Oops! AbstractSubItem should not be instantiated!");
}
this._name = build.name;
}
/*
AbstractSubItem implements a different version of the Builder that also requires a name parameter.
*/
static get Builder() {
/*
This builder inherits from the builder used by AbstractItem by calling the Builder getter function and thus retrieving the constructor.
*/
class SubItemBuilder extends super.Builder {
constructor(id, name) {
super(id);
if (!name) {
throw new TypeError('A subitem should always have a name!');
}
this._name = name;
}
get name() {
return this._name;
}
}
return SubItemBuilder;
}
get name() {
return this._name;
}
}
class ConcreteItem extends AbstractItem {
doSomething() {
console.log('Hello world! My name is ' + this.id + '.');
}
}
class ConcreteSubItem extends AbstractSubItem {
doSomething() {
console.log('Hello world! My name is ' + this.name + ' (id: ' + this.id + ').');
}
}
new ConcreteItem.Builder(1).build().doSomething();
new ConcreteSubItem.Builder(1, 'John').build().doSomething();
In my opinion, there are some pros and cons to my current approach.
Pros
The Builder() method provides a common interface that can be used to obtain a builder for all implementing classes.
My concrete classes can inherit the builder class without any additional effort.
Using inheritance, the builder can be easily expanded if needed.
The builder code is part of the abstract class, so it is clear what is being built when reading the code.
The calling code is easy to read.
Cons
It is not clear, looking at the Builder() getter function, which parameters are required to avoid an exception. The only way to know this is to look at the constructor (or at the comments), which is buried a couple of layers deep.
It feels counter-intuitive having the SubItemBuilder inherit from super.Builder, rather than a top-level class. Likewise, it may not be clear to other how to inherit from the ItemBuilder without looking at the SubItemBuilder example.
It is not really clear, looking at the AbstractItem class, that it should be constructed using the builder.
Is there a way to improve my code to negate some of the cons I've mentioned? Any feedback would be very much appreciated.

Related

Efficient and elegant way to create nested ES6 classes?

While trying to find a way to use nested classes in JS, I came up with this sort of thing:
class Character {
constructor() {
this.Info= class I {
constructor(name,value) {
this.name=name;
this.value=value;
}
};
}
bar () {
var trial = new this.Info("Goofy", 2);
alert(trial.name);
}
}
var test = new Character();
test.bar();
and it seems to work. However, I'm afraid this might be creating a new function object for each new call, as I define the class in the constructor (which is executed at each new call). Is there a more efficient way of doing this?
This question does not solve my issue as the author only wonders how to even have a nested class; I'm already able to do that but I wonder if there's a more efficient way.
Using a static property in react, angular or just using babel, because direct static class properties are not currently implemented on all browsers.
class Character {
static Info = class I {
constructor(name) { this.name=name; }
}
bar () {
return new Character.Info("Goofy");
}
}
const test = new Character();
console.log(test.bar());
Using a static property the old way -- currently working on all browsers.
class Character {
bar () { return new Character.Info("Goofy"); }
}
Character.Info = class I {
constructor(name) { this.name=name; }
}
const test = new Character();
console.log(test.bar());
Maybe the example you've given is too simple to demonstrate whatever problem you're trying to solve, but it seems to me you don't need to nest them at all.
class Info {
constructor(name, value) {
this.name = name;
this.value = value;
}
}
class Character {
bar() {
var trial = new Info("Goofy", 2);
console.log(trial.name);
}
}
const test = new Character();
test.bar();

Defining JavaScript functions inside vs. outside of a class

I'm trying to figure out if there's any different when defining functions inside or outside of a class in JavaScript. Why would I choose to do it one way over the other? (Notice my getName [inside class] and getName2 [outside of class]).
class TestClass {
constructor(myName) {
this.name = myName;
}
getName() {
return this.name;
}
}
TestClass.getName2 = function() {
//won't actually print the name variable set since not associated with an instance of the class?
console.log(this.name);
};
var test = new TestClass("Joe");
console.log(test.getName());
///////////////
TestClass.getName2();
Output:
Joe
TestClass
The only difference I can really see so far through my testing here is that I cannot access this.name within my getName2 since I believe it's not associated with any instance of the TestClass. So my getName2 is almost like a static class function where it's not associated with an instance of the class?? Please help me clarify this and why I would choose to implement a function one way over the other.
From the MDN doc:
JavaScript classes, introduced in ECMAScript 2015, are primarily syntactical sugar over JavaScript's existing prototype-based inheritance. The class syntax does not introduce a new object-oriented inheritance model to JavaScript.
So this...
class TestClass {
constructor(myName) {
this.name = myName;
}
getName() {
return this.name;
}
static getName2() {
return 'getName2 result';
}
}
...is exactly equivalent to this:
const TestClass = function(myName) {
this.name = myName;
}
TestClass.prototype.getName = function() {
return this.name;
}
TestClass.getName2 = function() {
return 'getName2 result';
}
So whether you use the older prototype syntax or the newer ES6 class syntax is just a matter of personal preference, and as you suspected, defining methods directly on a class is exactly equivalent to creating a static class method.
I don't think there's any reason you would ever do
TestClass.getName2 = function() {
If you want a standalone function, make it a standalone function.
e.g.
export function getName2() {...};
export TestClass;
If you want to extend an existing class, you'd do
TestClass.prototype.getName2 = function() {
which will allow you to access this.
One thing that I noticed is you are using a reserved keyword for objects, properties, and methods. TestClass.getName2() should return undefined, but since you used the "name" reserve keyword. It returned the name of the class, which is considered an object like a function. In your case, you are using this.name which refers to the class name and it returns "TestClass".
Example:
class TestClass {
constructor(userName) {
this._userName = userName;
}
getName() {
return this._userName;
}
}
TestClass.getName2 = function() {
// returns undefined now
console.log(this._userName);
};
var test = new TestClass("Joe");
console.log(test.getName()); // returns Joe
///////////////
TestClass.getName2(); // returns undefined
let testClassName = TestClass.name; // Just a little proof of what is returned below
console.log(testClassName); // returns TestClass
My only suggestion is that you should stay abreast of the current reserved keywords and where you can and cannot use them. You could also focus on your naming convention in your code.

Javascript - should i use inheritance model where i require a specific interface?

I'm in a bit of a dilemma. I am using ES6 classes for building a simple class hierarchy. One base class with a method, 3 subclasses that override that method. At some some point, i am traversing a list of instances where i call this method. Technically i don't need the class hierarchy if i can on the other hand simply guarantee that all my objects (any kind of objects) provide a method with that name. Which is the better approach?
class Base {
constructor() {
}
method() {
console.log('base')
}
}
class Subclass1 extends Base{
constructor() {
super()
}
method() {
console.log('sc1')
}
}
class Subclass2 extends Base{
constructor() {
super()
}
method() {
console.log('sc2')
}
}
class Subclass3 extends Base {
constructor() {
super()
}
method() {
console.log('sc3')
}
}
classInstances = [new Subclass1(), new Subclass2(), new Subclass3()];
classInstances.forEach(instance => {
instance.method();
})
// or using any kind of objects...
obj1 = {
method() {
console.log('obj1');
}
}
obj2 = {
method() {
console.log('obj2');
}
}
obj3 = {
method() {
console.log('obj3');
}
}
objectInstances = [obj1, obj2, obj3];
objectInstances.forEach(instance => {
instance.method();
})
Yes, just program to the interface.
You are not using Base.prototype.method anywhere, it's pretty pointless - so drop it. And then Base is just an empty class, pretty much equivalent to Object, so pointless as well. You are not actually using any inheritance features here, do you don't need a class hierarchy either.
A test for x instanceof Base is the only use case left, but then again typeof x.method == "function" is much easier and more flexible.
Javascript uses 'prototypal inheritance', rather than the classical inheritance of other object orientated languages. It's a reasonable approach to test whether an object is based on a given prototype, but given that javascript objects can be freely 'patched' to override the prototype, or even mutate the prototype, you need to be careful about what you assume about an object.
If you expect a given method to exist, then it might be more reliable to test specifically for that method, but sometimes that doesn't read so well in code.
One problem you'll encounter is creating 'base' classes that define a type but which has no default behaviour. For example, here we could create a base type for Animal or Speakable, but the speak method would need to be a no-op or throw an error if ever called. That might of course be useful during development for catching when you fail to define an expected behaviour.
Also, Javascript doesn't allow multiple inheritance, so you can't implement 'interfaces' as you might in other languages, although you can create composite prototypes that achieve a similar result.
class Thing {
constructor(name) {
this.name = name
}
}
class Dog extends Thing {
speak() {
return 'woof';
}
}
class Cat extends Thing {
speak() {
return 'meow';
}
}
class House extends Thing {
}
const things = [new Dog('Rover'), new Cat('Tibbles'), new House('Foo Hall')];
things.forEach(thing => {
if (typeof thing.speak === 'function') {
console.log(`${thing.name} says '${thing.speak()}'`);
} else {
console.log(`${thing.name} cannot speak`);
}
});

javaScript - multiple inheritance in ES6,

I'm trying to inherit class "EventEmitter" and a pre defined class "Person", here is the code
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
introduces() {
return `My name is ${this.name}. I am ${this.age} years old.`;
}
};
\\here comes the mixin part
function mix(...mixins) {
class Mix {}
for (let mixin of mixins) {
copyProperties(Mix, mixin);
copyProperties(Mix.prototype, mixin.prototype);
}
return Mix;
}
function copyProperties(target, source) {
for (let key of Reflect.ownKeys(source)) {
if (key !== "constructor" && key !== "prototype" && key !== "name") {
let desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}
I intend to create a new class 'PersonWithEmitter', and still call the constructor like below:
class PersonWithEmitter extends mix(Person,EventEmitter){
constructor(name,age){
super(name,age)
\\do something else
}
Here comes the issue, when I create a new instance of 'PersonWithEmitter' like this let someOne = new PersonWithEmitter("Tom",21), will not get what I what, In the new class, I want to use this.name and this.age, which is still undefined.
So how can I change my code, So the new class can both have its parent's methods and only class "Person"'s constructor?
Pardon me for my broken English.
In many cases multiple inheritance in JavaScript indicates wrong design decision. It may result in hacky objects that don't behave as they should. The way it is done should always be determined by particular objects. In some cases a shallow copy of own properties is needed, in another the entire prototype chain should be traversed. Composition over inheritance is often a better choice.
The problem in the code above is that class constructors are not called. Mix has empty constructor. This is the reason why PersonWithEmitter doesn't work as expected.
Multiple constructor function calls can generally be stacked like:
function Foo(...args) {
let _this = this;
_this = Bar.apply(_this, args);
_this = Baz.apply(_this, args);
return _this;
}
This won't work if Bar or Baz is ES6 class because it contains a mechanism that prevents it from being called without new. In this case they should be instantiated:
function Foo(...args) {
copyProperties(this, new Bar(...args));
copyProperties(this, new Baz(...args));
}
Static and prototype properties may also be copied to Foo like is shown in code above.
If the case is narrowed down to Node.js EventEmitter, it can be handled like a special case. Its implementation is certain and stable. It is already known that EventEmitter does initialization in constructor, it has a shallow prototype chain and property descriptors. So it likely should be:
class Foo extends Bar {
constructor(...args) {
super(...args)
EventEmitter.call(this);
// or
// EventEmitter.init.call(this);
}
copyProperties(Foo.prototype, EventEmitter.prototype);

javascript - Check if parent methods are used inside child methods

I'm writing some JS that extends a parent class and I wanted to know if there's a way to tell if a child class is using a parent method without having called it yet. Ideally I'd like to run a check in the constructor of the parent to see if any of the child methods are using the parent's methods in the method definition.
I've done a bit of research and have come across things like Object.getOwnPropertyNames() but I'm not sure if I'm headed in the right direction.
For instance:
class Path {
constructor (name) {
// how can I check if addRelationship have been used? If possible.
this.relationships = {};
this.currentRelationship = '';
this.path = path;
}
addRelationship (relationship) {
// do something
this.currentRelationship = relationship.path;
return this;
}
makePath () {
let path = [this.path];
if(this.currentRelationship) {
path.push(this.currentRelationship)
}
return path.join("/");
}
}
class OnePath extends Path {
// ...
someMethodFromThatRelationship () { }
}
class TwoPath extends Path {
// ...
}
var onePath = new OnePath('one');
var twoPath = new TwoPath('two-path');
class SomeOtherPath extends Path {
one () {
return this.addRelationship(onePath);
}
two () {
return this.addRelationship(twoPath);
}
}
The idea of the above example is I could check if addRelationship is referenced in any methods and if so, register a this.relationships.one and this.relationships.two before one() and two() are actually called. I hope I'm making sense. I'd love to know if this is even possible.
Updated
The end result of the above code would be the ability to do the following:
let someOtherPath = new SomeOtherPath('some-other-path');
// now I can call
someOtherPath.relationships.one.someMethodFromThatRelationship();
// and can also call the save method from the extended class
someOtherPath.one().makePath();
// some-other-path/one
// I can also just call
someOtherPath.makePath();
// some-other-path
Is there a way to tell if a child class is using a parent method without having called it yet?
No. Figuring out what programs do without calling them is equivalent to the unsolvable halting problem.
I think what you are actually looking for is a more declarative approach for creating the relationship and its accompanying method in one go. Don't use too much magic (which a parent constructor inspecting its child class code would certainly be) but be explicit.
class Path {
constructor (path) {
this.relationships = {};
this.currentRelationship = '';
this.path = path;
}
addRelationship (name, relationship) {
this.relationships[name] = relationship;
this[name] = function() {
// do something
this.currentRelationship = name;
return this.relationships[name];
}
return this;
}
makePath () {
let path = this.path;
if (this.currentRelationship) {
path += "/" + this.relationships[this.currentRelationship].makePath();
}
return path;
}
}
class SomeOtherPath extends Path {
constructor(name) {
super(name);
this.addRelationship("one", new OnePath('one'));
this.addRelationship("two", new TwoPath('two-path'));
}
}
or even
class Path {
constructor (path, relationships = {}) {
this.relationships = relationships;
this.currentRelationship = '';
this.path = path;
for (let const r in relationships)
this.addRelationship(r, relationships[r]);
}
…
}
class SomeOtherPath extends Path {
constructor(name) {
super(name, {
one: new OnePath('one'),
two: new TwoPath('two-path')
});
}
}
Maybe you don't even need these child classes any more if they don't have other methods or are only instantiated once (as singletons).
Notice that the above approach will create new methods and new subpaths on every instantiation of the constructor, if you don't want that you can of course also put the declaration on the class statically. Just make addRelationShip a static method that initialises the default relationships objects and puts the methods on the class' .prototype. The variations of the pattern are endless.
You even might want to experiment with the proposed decorators feature for classes.

Categories

Resources