skip to Main Content

I need to define a function that can be used by multiple classes, but as far as I understand, inheriting from a superclass doesn’t work for me. Essentially, what I would like to achieve is the ability to extend multiple interfaces for each class.

For example, if I have defined classes Apple, Orange, Banana, I want all of them to have an identical isFresh() function. I also like to let Apple, Orange, and Earth to have a getRadius() method. This is somewhat similar to Apple interface Fruit, SphericalObject {...} I also want to be able to override the functions if I want to. However, inheritence doesn’t work for me because I would like to inherit from multiple superclasses.

What is the best way to achieve this?

I am aware of this similar post, I understand from that JavaScript is dynamically typed and does not have interfaces, and the suggested Duck Type doesn’t seem to solve my problem. I don’t really care to check if the method in interface exist in child classes.

3

Answers


  1. Looks like you’re looking for "mixins". They are not built-in in javascript, but are quite easy to implement in userland, for example:

    function augment(cls, ...mixins) {
        return class extends cls {
            constructor(...args) {
                super(...args)
    
                for (let c of mixins)
                    for (let p of Object.getOwnPropertyNames(c.prototype))
                        if (p !== 'constructor')
                            this[p] = c.prototype[p]
            }
        }
    }
    
    //
    
    class Apple {}
    
    class PhysicalObject {
        isFresh() {
            return 'hey'
        }
    }
    
    let AppleWithObject = augment(Apple, PhysicalObject)
    let x = new AppleWithObject()
    console.log(x.isFresh())
    Login or Signup to reply.
  2. You only needs a single extends to achieve your result.

    class PhysicalObject {
       constructor(x,y) {this.x=x;this.y=y;}
       getPosition() {return {x:this.x,y:this.y}}
       displayPosition() {console.log(this.getPosition().x+', '+this.getPosition().y)}
       }
       
    Earth=new PhysicalObject(0,0);
    Earth.displayPosition();
    
    class Fruit extends PhysicalObject {
      constructor(x,y,a) {super(x,y);this.age=a;}
      isFresh() {return this.age<7}
      }
      
    Apple=new Fruit(1,1,6);
    Apple.displayPosition();
    console.log(Apple.isFresh());
    Login or Signup to reply.
  3. On the risk of being misprised extensively: inspired by Douglas Crockford I stopped using classes or prototypes (well, classes I never used in ES, never had any use for it).

    Instead I create factory functions. Here’s an examplary Fruit factory.

    To play with the idea I created a small Stackblitz project, with a more generic approach.

    const FruitStore = FruitFactory();
    
    FruitStore.apple = { 
      mustPeal: false, color: `red`, fresh: "Nope", origin: `Guatamala`, 
      inheritsFrom: {
        ...PhysicalObjectFactory(true), 
        ...ChemicalObjectFactory(true, null, true) },
    };
    FruitStore.orange = { inheritsFrom: {
      origin: `Spain`, fresh: false, color: `orange` } 
    };
    FruitStore.pineapple = { color: `yellow`, spherical: false, qty: `200Kg` };
    console.log(FruitStore.all);
    FruitStore.orange.fresh = `UNKNOWN`;
    console.log(FruitStore.orange);
    
    function PhysicalObjectFactory(spherical) {
      return { isPhysical: true, isSpherical: spherical };
    }
    
    function ChemicalObjectFactory(
      carbonBased = null, boilingPoint = null, solid = null) {
      return { carbonBased, boilingPoint, solid };
    }
    
    function FruitFactory() {
      let allFruits = {};
      // all fruits 'inherit' these properties
      // you can override any of them on
      // creating a fruit instance
      const fruitDefaults = {
        mustPeel: true,
        fresh: true,
        qty: `unset`,
      };
      const instance = { get all() { return allFruits; }, };
      // the proxy ensures you're working on the `allFruits` instance
      const proxy = { 
        get (obj, key) { return allFruits[key] ?? obj[key]; },
        set(_, key, props) { 
          allFruits[key] = createFruitInstance(key, props); 
          return true; 
        },
      };
      
      return new Proxy(instance, proxy);
      
      function createFruitInstance(name, props = {}) {
        const fruit = { name };
        let inherits = {};
        let inheritsFrom = { ...props.inheritsFrom };
        delete props.inheritsFrom;
        Object.entries({...fruitDefaults, ...props, ...inheritsFrom})
        .forEach( ([key, value]) => 
          value || key in fruitDefaults ? fruit[key] = value : false 
        );
        return fruit;
      }
    }
    .as-console-wrapper {
        max-height: 100% !important;
    }
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search