Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find all classes in a Javascript application that extend a base class

I have code like this

class Animal{}
class Dog extends Animal {}
class Cat extends Animal {}
class Donkey extends Animal {}

I want to look at all of the classes in my application's universe, and when I find one that descends from Animal, I want to create a new object of that type and add it to the list. This allows me to add functionality without having to update a list of things. So I can avoid the following:

var animals = [];
animals.push( new Dog() );
animals.push( new Cat() );
animals.push( new Donkey() );

PS: I don't want to add extra functionality to my classes or call them explicitly.

like image 467
obenjiro Avatar asked Jul 24 '15 19:07

obenjiro


4 Answers

What about this:

class Animal {
  static derived = new Set();
}
class Dog extends Animal {
  static dummy = Animal.derived.add(this.name);
}
class Cat extends Animal {
  static dummy = Animal.derived.add(this.name);
}
class Donkey extends Animal {
  static dummy = Animal.derived.add(this.name);
}

console.log(Animal.derived);

I tried this in a TypeScript environment. The result:

Set(3) {"Dog", "Cat", "Donkey"}

without instantiating a class.

like image 149
Denis Giffeler Avatar answered Sep 27 '22 21:09

Denis Giffeler


I think you could make use of decorators. For instance, you could create @Extends() one and provide base class as an argument, e.g. @Extends(Animal). Inside the decorator function, you could take the name of the class decorated with @Extends and and put it into an array or an object. Don't know if it is applicable in browsers, but it should be. In Node with TypeScript I would do something like:

import { MyClassMetadata } from './';

export function Extends(parent): (...args: any[]) => void {
    return (target: object): void => {
        MyClassMetadata.someVariableToStoreChildren[parent.constructor.name].push(
            target,
        );
    }
}

Then you can access the MyClassMetadata variable that stores array of children of a given class and use it the way you want. You can play with it and get the desired result.

like image 28
Sergey Orlov Avatar answered Sep 27 '22 19:09

Sergey Orlov


Here what I discovered so far http://jsbin.com/xiroyurinu/1/edit?js,console,output

class Animal{}
class Dog extends Animal {}
class Cat extends Animal {}
class Donkey extends Animal {}

var animals = getAllSubclasses(Animal);

console.log(animals.map(function(c){ return new window[c] })) // creates objects
document.body.innerText = animals; // Dog, Cat, Donkey

and the magic

function getAllSubclasses(baseClass) {
  var globalObject = Function('return this')(); 
  var allVars = Object.keys(globalObject);
  var classes = allVars.filter(function (key) {
  try {
    var obj = globalObject[key];
        return obj.prototype instanceof baseClass;
    } catch (e) {
        return false;
    }
  });
  return classes;
}

The main disadvantage of this method is that I can not use ES6 module import and have to do old fashioned and simple contatenation of files, but this is still better that nothing.

PS: still wait for better answer

UPD: and ye, i know that to use this all classes must be defined globally, that's why i search for better way to do this..

like image 42
obenjiro Avatar answered Sep 27 '22 19:09

obenjiro


It is not possible. You can never know e.g. about local classes defined inside some function, or privately in another module. And that's by design. It would be unmodular and break encapsulation.

Also, the set of classes is not static in JavaScript. You can create new classes dynamically open-endedly.

If you think you want such functionality then I strongly suggest you're holding it wrong. What are you trying to achieve?

like image 34
Andreas Rossberg Avatar answered Sep 27 '22 19:09

Andreas Rossberg