How does one avoid naming collisions from class inheritance in ES6 Javascript?
Large ES6 Javascript applications use a lot of inheritance, so much so that using generic names in a base class can mean debugging headaches later when creating derived classes. This can be a product of poor class design but seems more to be a problem with Javascript being able to scale up smoothly. Other languages provide mechanisms to hide inherited variables (Java) or properties (C#). Another way to alleviate this problem is to use private variables which isn't something Javascript has.
Here's an example of such a collision. A TreeObject class extends an Evented object (to inherit evented functionality) yet they both use parent to store their parent.
class Evented {
constructor(parent) {
this.parent = parent;
}
}
class TreeObject extends Evented{
constructor(eventParent, treeParent) {
super(eventParent);
this.parent = treeParent;
}
}
While this example is a bit artificial, I've had similar collisions in large libraries like Ember where terminology between the library and the end application overlap quite a bit causing me hours wasted here and there.
This really seems to be a design issue (use smaller objects and flatter hierarchies), but there's a solution to your problem as well: symbols!
const parentKey = Symbol("parent");
export class Evented {
constructor(parent) {
this[parentKey] = parent;
}
}
import {Evented} from "…"
const parentKey = Symbol("parent");
class TreeObject extends Evented {
constructor(eventParent, treeParent) {
super(eventParent);
this[parentKey] = treeParent;
}
}
They will reliably prevent any collisions, as all symbols are unique, regardless of their descriptors.
Large ES6 JavaScript applications use a lot of inheritance.
Actually, most of them don't. In a framework like Angular, one level of inheritance from the platform-defined classes is most common. Deep inheritance structures are brittle and best avoided. A large app might have some cases of two user levels of classes, A > B, where A contains a bunch of common logic and B1 and B2 are light specializations, such as a different template for a component, at a level that does not give rise to concerns about collisions.
Your example of Evented is semantically not really a parent class; it's more in the nature of a mixin. Since JS doesn't really handle mixins well, instead of deriving Tree from Evented, I'd hold the evented object as a property:
class TreeObject {
constructor(eventParent, treeParent) {
this.evented = new Evented(eventParent);
this.parent = treeParent;
}
send(msg) { this.evented.send(msg); }
}
If you really want to design Evented for use as a mixin-like superclass, then it's the responsibility of the designer to make member variables as unique as possible, as in
export class Evented {
constructor(parent) {
this.eventedParent = parent;
}
}
or use a symbol, as proposed in another answer. Or, consider using a map:
const parents = new Map();
class Evented {
constructor(parent) {
parents.set(this, parent);
}
sendParent(msg) {
parents.get(this).send(msg);
}
}
I've had similar collisions in large libraries like Ember
I haven't. Ember classes typically define few members, and the methods they define are well-defined and well-known.
Of course, the real solution is to use a typing system such as TypeScript. It does provide private members/methods. If a method does need to be public, TS will not let you define a method by the same name on the subclass unless the signatures match.
One possible way of minimizing this, that I can think of right the top of my head, is wrapping the internal state of your object, for your example that would be:
class Evented {
constructor(parent) {
this.eventedState = {
parent : parent
}
}
}
And apply the same pattern to all the classes. Obviously this still means that you have one property per object that might collide, but it reduces the chance of collision. The downside of this is that its not perfect and refactoring your code to use this pattern might be quite painful.
Related
I'm looking for a way to implement an abstract static method in TypeScript.
Here an example:
abstract class A {
abstract static b (): any // error
}
class B extends A {
static b () {}
}
This is allowed with public, private and protected methods but not with a static method. TypeScript returns the following error:
'static' modifier cannot be used with 'abstract' modifier.ts(1243)
Is there any workaround?
I think the reasoning is generally as follows:
Abstract methods are placeholders that a concrete subclass is supposed to implement.
The abstract base class will have a concrete method which uses this abstract placeholder, and thus requires it to be implemented; without this, abstract methods make little sense at all. E.g.:
abstract class Foo {
bar() {
return this.baz() + 1;
}
abstract baz(): int;
}
This can only be called on a concrete instance, e.g.:
function (foo: Foo) {
let val = foo.bar();
...
}
The result will be different here depending on what concrete subclass of Foo you get, but it's all guaranteed to work.
Now, what would this look like with static methods?
abstract class Foo {
static bar() {
return this.baz() + 1;
}
abstract static baz(): int;
}
function (F: typeof Foo) {
let val = F.bar();
...
}
This looks somewhat logical and like it should work, shouldn't it? But:
If you write this, there's basically no difference to passing instances of classes, except for the awkward typeof typing. So, why?
If you never instantiate the class, what's the point of a class?
If your static method does instantiate an object, if it acts as an alternative constructor—which is entirely legitimate—then calling F.bar() to get a new instance, you'd expect to get an instance of F, but you might be getting an instance of some subclass of it. And that's arguably not desired.
To rephrase the chain of argument here:
If you're going to use static methods at all, they should act as alternative constructors and return an instance, otherwise they have little business being part of the class.
Alternative constructors should return an instance of the class that you called them on. Calling F.bar() and not getting an instance of exactly F is… weird?
If you're going to call static alternative constructors on classes, you're going to want to call them on specific classes and not variables as shown above, because otherwise you really won't know what you're getting (see point above).
Therefore, there's no real use case for an abstract static method, either as direct alternative constructor nor as helper function for one, since that would lead to a violation of one of the above points one way or another.
As far as I see, this is pretty much the classical thinking that lead to abstract static methods not being a thing. Certain technical limitations in Java may have contributed to that and/or lead to said technical limitations in the first place.
In Javascript one can argue that this makes less sense, since even classes are objects and it would be entirely feasible to use them as described above, and in certain situations it may even make sense. But here we are.
TLDR: Are there any pitfalls in using this.constructor(props) in a class method to duplicate that class?
I have a use case where defining class models for entities proves quite useful for management and processing throughout a complex system.
These entity model classes all require a set of common methods.
I do not wish to duplicate code across multiple entity model classes. I am exploring the use of a BaseEntityModel class which can be extended by all other model classes requiring the common functionality.
One of these methods is a clone() method, which provides a deep clone of the object as the ExtendedEntityModel type.
clone() will be declared on the BaseEntityModel class. I want to preserve the class name and properties when calling clone from a ExtendedEntityModel object which has extended BaseEntityModel class.
I need a way of creating a new instance of a ExtendedEntityModel through the BaseEntityModel without having to explicitly know what it is when calling clone(). In otherwords, I wanted to avoid using new ParentClassEntity() because the BaseEntityModel methods need to work for all extensions of it.
I did some searching but could not find much on this pattern. After some tinkering, I found that calling this.constructor(props) does exactly what I want. See below class, and clone() function
BaseEntityModel.js
class BaseEntityModel {
constructor({ entityType, dataKeys }) {
this.entityType = entityType;
this.dataKeys = dataKeys;
}
/**
* Provide deep clone of self, Preserving extended class properties
*/
clone() {
return new this.constructor(JSON.parse(JSON.stringify(this))); //<-- here, this.constructor
};
}
ExtendedModel.js
class ExtendedModel extends BaseEntityModel {
constructor({param1, param2} : ExtendedModel) {
super({ entityType, dataKeys });
this.param1 = param1;
this.param2 = param2;
}
}
and then usage
var extendedModel1 = new ExtendedModel({param1, param2};
var clonedExtendedModel = extendedModel1.clone(); //returns a cloned version and of type ExtendedModel
I cannot find much on calling this.constructor. Can anyone provide some insight into why this might be a bad choice, or confirm that this is not a javascript bomb waiting to go off?
See CodeSandbox of working implementation
I am learning about compositions in Javascript. So I want to ask if this is correct way of doing things.
I made some exercises that look like this:
class Animal {
// constructor() {
// }
eat = () => {
console.log("this creature is eating");
}
}
const AnimalsWithWings = superclass => class extends superclass {
constructor(Feathers, ...args) {
super(...args);
Object.assign(this, { Feathers });
}
}
const CanDive = superclass => class extends superclass {
// constructor( ...args) {
// super(...args);
// }
dive = () => {
console.log("Can Dive");
}
}
class Duck extends AnimalsWithWings(CanDive(Animal)) {
constructor(eats, ...args) {
super(...args);
Object.assign(this, { eats });
}
}
const duffy = new Duck("watermelon", true);
console.log(duffy);
duffy.dive();
duffy.eat()
I am still in learning process so I just need some pointers.
Did it do more or less what you expected? Then, sure, it's a correct way to do it, whatever "correct" means here.
It looks to me like it does what it was meant to do when I pop it into the console. I can't really say much more on your code specifically because I'm not sure what concrete domain it's trying to model, aside from maybe breaking down Ducks into atomic pieces.
If you're going to do it this way, though, I'd personally prefer to use a params object instead of just changing the constructor signature like that with AnimalsWithWings. That way, the order of extra parametrizations doesn't depend on the order in which the mixins were applied, which I would consider a Surprise. Surprises are bad.
const AnimalsWithWings = superclass => class extends superclass {
// Everyone receives the same `params` object.
// They only take what they know about, and ignore the rest.
constructor(params) {
super(params);
Object.assign(this, { Feathers: params.Feathers });
}
}
Even more personal opiniony, I'd name them WithDiving and WithWings instead, just to keep a somewhat consistent naming scheme, and to better imply that these are modifiers, not "real" base classes.
Your code does saddle every Duck with a prototype chain 4 prototypes long, but eh, whatever. If it somehow becomes a performance problem then you can create a utility function to optimize the mixin process or something. Flatten the prototypes, maybe.
Your code does also let you call super.method() in methods, though it's debatable whether you should ever use that in a mixin at all. I'd say you shouldn't, unless you want your mixins to implicitly depend on each other, which is a Surprise.
There are plenty of other ways of doing mixins, too.
You could create a utility function to flatten all the prototypes into a single new one and return a base class from that which you extend. (Just be sure to iterate property descriptors rather than just using Object.assign() when doing that flattening, if you want to properly handle things like get/set accessors, etc.)
You could eschew Classes and just directly create prototype objects and use Object.create() to create instances. (same thing about iterating property descriptors.)
You could create a Duck prototype using a bunch of iterative calls to Object.create() instead of iteratively extending base classes.
You could control the additional behaviors with helper Controller Classes instead of composing behavior directly into the base.
You could deal just in plain objects with data, and pass the objects to functions that expect the object to have certain properties on it in order to do things. (Amusingly, called "duck typing") I'll grant that's not really mixins, just calling functions, but if it accomplishes the same thing in effect...
Probably a bunch others I can't really think about at the moment. It's all sticking sets of behaviors onto some base thing.
I know there are tons of oop posts on here as well as many blogs etc. I'm not posting this because im too lazy to read other posts, I probably read about 100 and still have some questions as many just state things but dont explain them much or certain things have contradicting answers and it would be great if I could get clear answers!
Encapsulation
There are posts that say yes to that because they define it as grouping together properties and functions. If that would be the definition, I totally get why javascript supports encapsulation. However, there also posts about keeping members private. As I understand (dont know java) but in Java thats the concept - you cannot access properties without methods (so only methods can access them) plus you actually have 'private/public' keywords to keep things private.
before ES6 :
Its possible:
function A (name) {
var name = name;
this.getName = function () {
return name;
}
}
var a = new A("mike");
a.getName() //accesses name
a.name //does not --> results in undefined
In es6 its not really possible, because you you would have to add the method accessing the property inside the constructor, which you wouldnt do
class A {
constructor(name) {
var name = name;
this.getName = () => {
return name; // only like this could you have var ...
}
}
}
So my question:
1. What is encapsulation ? Is it about grouping methods/properties or is it about keeping members private (which wouldnt be possible in es6 and before es6 not by default)?
Abstraction
" hiding internal implementation details. It should only reveal operations relevant for the other objects "
I would imagine for example jquery methods to use abstraction and you dont see their inner implemantion, 'just use' them. Could someone please explain and provide an example im vanilla js what abstraction is and if javascript actually makes use of that?
polymorphism
"designing objects to share behaviors and to be able to override shared behaviors with specific ones"
overriding
overloading
Many posts/blogs say polymorphism is NOT used in javascript, but..
Overloading is without argument not possible in javascript. Overriding of methods is?! I can have a parent class and a child class having the same function (same name) doing different things. Is that what you understand under overriding? Also, sharing behaviors is def. possible but only with inheritance.
Is there a way to share behaviors without inheritance, but only with making use of polymorphism? Im generally confused what exactly polymorphism is after reading for a while..
Inheritance
Inheritance is def. supported by js.
I just want to double check about es6 classes that I get it right (i have no experience with java etc).
The difference is that js uses prototypical inheritance and not classical as java etc. The difference is that when I create a new instance/=object from my es6 class, it links to properties/methods of the class that I use to initialize new objects, while in classical inheritance, the engine copies the properties/methods from that class. s that correct? Could someone please explain the difference - resulting - from that difference?
Imagine I have two classes
class Point {
}
class EditablePoint extends Point {
// list of additional properties and methods
}
function edit(editablePoint) {
//
}
let point = new Point();
edit(point);
How can I augment all objects with another class methods/properties?
Any prototype hacks don't work for me. It seems I can't get around this instead of recreating the objects or assigning manually additional properties of the class or using a loop. I would like some one line clean solution.
Disclaimer: this looks like an antipattern with which I agree however I have huge arrays of Points and only in super rare occassions I need to edit them and I want to do so in-place without introducing any copying as arrays are HUGE.
Why I don't like having all points have editable properties and want to have them on the prototype:
each object instance becomes larger and that's the least I want
it doesnt communicate well to the code reader
What you're asking is technically feasible:
let pointProto = Point.prototype;
let editProto = EditablePoint.prototype;
for (let memberName in editProto) {
if (typeof editProto[memberName] === 'function'
&& !(memberName in pointProto)) {
pointProto[memberName] = editProto[memberName];
}
}
This will achieve what you're describing: all instances of the Point class will now have the same methods as EditablePoint instances do (the !(memberName in pointProto) clause ensures we don't overwrite methods that were already defined).
However, as you yourself have said, this is a massive antipattern, since at this point you're blurring the line between the two classes to a point where it's unclear why they even exist as separate classes; seeing an array of Points being used as if they were EditablePoints is a 100% sure way to confuse anyone who's reading your code. If you think you need something like this, it's probably better to rethink your model structure.
You could use Object.assign to add properties and setPrototypeOf to add methods by setting prototype of EditablePoint to prototype of Point, but since EditablePoint inherits from Point you will still have Point methods.
class Point {
constructor() {
this.point = true;
}
foo() {
console.log('foo')
}
}
class EditablePoint extends Point {
constructor() {
super();
this.editable = true;
}
bar() {
console.log('bar')
}
}
function edit(editablePoint) {
Object.assign(editablePoint, new EditablePoint);
Object.setPrototypeOf(editablePoint, EditablePoint.prototype)
}
let point = new Point();
edit(point);
console.log(point)
point.foo();
point.bar();
I think you just should not make an EditablePoint class that inherits from Point and has all the methods necessary for editing. Instead, use composition over inheritance, and make a class
class PointEditor {
constructor(point) {
this.point = point;
…
}
…
}
with all the methods necessary for editing the point associated to the editor. This way, you can instantiate as few / as many PointEditors for the points in your array that you want to edit, without changing anything about how the points in the array are created.