2

Note: this is not a duplicate. The question linked below asks simply about class variables, and not specifically about a workaround to allow inheritance. I apply the answers in that question to a potential inheritance solution, without success.

What I need to do: basic property overrides:

class ProductA {
    weight: 5,
    height: 10,
    price: 7
}

class ProductB extends ProductA {
    height: 12
}

class ProductC extends ProductA {
    price: 12,
    weight: 2
}

class ProductC2 extends ProductC {
    height: 5
}

We can't do this with ES2015???

According to this question, properties aren't supported in ES2015. Those answers recommend:

Use the constructor?

class ProductA {
    constructor(){
        this.weight = 5;
        this.height = 10;
        this.price = 7;
    }
}

// and then...?

class ProductB extends ProductA {
    constructor(){
        super()
        this.height = 12;
    }
}

That might appear to work, until you want to add this.initialize() call in ProductA's constructor, for example... There's an ES2015 rule that says you have to call super() before anything else. For ProductB, you'd have to call super() before overriding the properties, which would be the wrong order (considering your initialize() logic uses those properties).


Use getters to map properties to the Class object?

class ProductA {
    get weight() {
        return this.constructor.weight;
    }
}

ProductA.weight = 5;
// also for height, price, and all other properties...?

I'm surprised this even works: when you extend the class, the getter somehow maps back to the base class, unless its own constructor has the property. It's almost like ProductB's prototype is ProductA.

The big problem here is to allow instance-level overrides (this.weight = 6). You'd have to add a setter method, and modify the getter to look at this.weight or fall back to this.constructor.weight. That's basically reimplementing simple inheritance. Why?!


This page has a better solution:

class ProductA {
    constructor(options) {
        Object.assign(this, {
            weight: 5,
            height: 10,
            price: 7
        }, options);
    }
}

class ProductB extends ProductA {
    constructor(options){
        super(Object.assign({
            height: 12
        }, options));
    }
}

There's still the problem of having to use "super()", which prevents me from overriding properties before running the init logic.

I was able to do this really easily in ES5. There has to be a better implementation or workaround?

Community
  • 1
  • 1
Michael Lewis
  • 4,252
  • 6
  • 28
  • 39
  • 3
    Given ES6 classes, it seems like the real answer here is "don't use `initialize()`". Calling child class methods before construction has completed is a recipe for trouble. – loganfsmyth Jul 15 '16 at 07:30
  • There are 2 parts of construction: setting up properties, and then doing something with them (I'll call it initialize, you call it whatever you want). Both of these parts should inherit from the base class, and be overridable. Clearly, this isn't possible without reverting back to the es5 way of assigning props to the prototype – Michael Lewis Jul 15 '16 at 14:29
  • Limitations around constructor ordering are not new. C++ for instance doesn't even allow you to call child class methods from parent classes in a constructor: https://stackoverflow.com/questions/496440/c-virtual-function-from-constructor It's not clear why the `initialize` logic you're talking about needs to be in a separate function. Why not keep it inside `constructor`? – loganfsmyth Jul 15 '16 at 16:55

2 Answers2

1

For constant properties, prototype may be used for all classes. this assignments in parent class constructor will override prototype assignments in child.

class ProductA { ... }

Object.assign(ProductA.prototype, {
    weight: 5,
    height: 10,
    price: 7
});

class ProductB extends ProductA {
    constructor(){
        super();
        ...
    }
})

Object.assign(ProductB.prototype, {
    height: 12
});

This worked in ES5 and this works in ES6. It is still JS, and classes are still glorified constructor functions, despite their restrictions.

The assumption that ES.Next class fields will work if initialize() is used in parent class is wrong. Class fields don't break the rules and are just sugar syntax for ES6 classes. They are added as this properties after super() call. As already stated in the comments, don't use initialize().

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • Whether the init logic is called initialize, or something else, I find it hard to believe that performing logic in the constructor is disallowed... then you'd always have to construct and call an init method externally, which doesn't solve anything. – Michael Lewis Jul 15 '16 at 14:24
  • The problem with putting those props in the constructor, is that you can't use super() to create a cascading or inheriting effect. – Michael Lewis Jul 15 '16 at 14:25
  • I'll accept this answer, because I think adding these props to the prototype is the best solution. This is absolutely absurd that they didn't allow properties. It will make things much less readable – Michael Lewis Jul 15 '16 at 14:26
  • Logic in constructor is allowed, sure, just keep in mind the order in which constructor code will be executed. Transpiled code helps a lot to figure the things out. – Estus Flask Jul 15 '16 at 15:15
  • Fortunately, we've got tomorrow's syntax today with Typescript and Babel. The same thing (assigning props to class prototype instead of class instance) could be neatly written with class decorator. If you have to fight a language or a framework, there is a considerable possibility that you've chosen the wrong way and experience XY problem. Feel free to ask an another practical question that reflects your real problem with class design. – Estus Flask Jul 15 '16 at 15:16
  • The proposed class properties syntax translates to the same code as the second example, so it doesn't address this issue any better than that one. – loganfsmyth Jul 15 '16 at 16:51
0

Maybe this would be a what you want, I think, but hard to work out what it is you actually want to do

class foo {
    constructor(){
      this.a = 1;
      this.b = 2;
      this.c = 3;
      if(this.construct){
         this.construct();
      }
      this.init()
   }

   get myState(){
       return this.a + ":" + this.b + ":" + this.c
   }
   init(){
       log(this.myState)
   }   
}


class bar extends foo{
    construct(){
       this.a = 10;
     }
}
class boo extends foo{
    construct(){
       this.b = 20;
    }
}

var a1 = new foo(); // 1 : 2 : 3
var a2 = new bar(); // 10 : 2 : 3
var a3 = new boo(); // 1 : 20 : 3
Blindman67
  • 51,134
  • 11
  • 73
  • 136