Search code examples
javascripttypescriptoopinheritancestorage

How to know what class inheritance is object from database


I have a base class that is used as a blueprint for other classes. Data of these classes are stored in SQL database and loaded when needed. The problem is how I can know what database object corresponds to what class.

So I have an animal class and 2 more classes (cat and dog) that extends an animal class. Dog and Cat classes will have all properties that base class have but they can hold different methods/functions. The database only stores data of these classes. Now the problem is when this data needs to be loaded how can the system tell if should create dog or cat class?

Here is some example code

const database_data = { name: "test", strength: 10 };

class Animal {
  public name: string;
  protected strength: number;

  constructor(name: string, strength: number) {
    this.name = name;
    this.strength = strength;
  }

  get getStrength(): number {
    return this.strength;
  }
}

class Dog extends Animal {
  constructor(name: string, strength: number) {
    super(name, strength);
  }

  wuf(): void {
    console.log(`Dog ${this.name} says wuf. Strength: ${this.strength}`);
  }
}

class Cat extends Animal {
  constructor(name: string, strength: number) {
    super(name, strength);
  }

  miau(): void {
    console.log(`Cat ${this.name} says miau. Cat is not strong ;)`);
  }
}

//Loading animals from database....
// const loadedAnimal = new Dog/Cat? how do I know(database_data.name, database_data.strength);
// console.log(loadedAnimal);

Solution

  • In order to not being forced of if...else or switch...case based maintenance ... "I would need to manually make ifs for each case. if(class === 'dog') new Dog(...) and so on. – kosta" ... the OP could go with an Object or Map based lookup approach embedded into a species aware factory function ...

    // the OP's refactored (typescript to es-6) class/subtype system.
    
    class Animal {
      #strength;
    
      constructor({ species = '', name = '', strength = 0 }) {
        Object.assign(this, { species, name });
        this.#strength = strength;
      }
      get strength() {
        return this.#strength;
      }
      valueOf() {
        return {
          species: this.species,
          name: this.name,
          strength: this.#strength,
        };
      }
      toJSON() {
        return this.valueOf();
      }
    }
    
    class Dog extends Animal {
      constructor(animalData = {}) {
        super({ species: 'dog', ...animalData });
      }
      wuf() {
        console.log(`Dog ${ this.name } says wuf. Strength: ${ this.strength }`);
      }
    }
    
    class Cat extends Animal {
      constructor(animalData = {}) {
        super({ species: 'cat', ...animalData });
      }
      miau() {
        console.log(`Cat ${ this.name } says miau. Cat is not strong ;)`);
      }
    }
    
    
    // (lookup table based) factory function.
    function createSpecies({ species = '', ...animalData }) {
      const Species = ({ // object-based species-lookup.
        cat: Cat,
        dog: Dog,
        '': Animal
      })[species];
    
      return (typeof Species === 'function')
        ? new Species(animalData)
        : null;
    }
    
    
    // helper function for exposing a value's (internal) class name.
    function getClassName(value) {
      const regXClassName =
        // ... [https://regex101.com/r/flUvPh/1]
        (/class\s+(?<customName>[^\s{]+)(?:\s+extends\s+\S+)?\s*\{|function\s+(?<builtInName>[^\s(]+)\s*\(/);
    
      let customName, builtInName;
      if ((value ?? null) !== null) {
    
        ({ customName, builtInName } = regXClassName
          .exec(String(Object.getPrototypeOf(value).constructor)).groups ?? {});
      }
      return customName || builtInName || (/\[object\s+(?<className>[^\]]+)\]/)
        .exec(Object.prototype.toString.call(value))
        ?.groups.className;
    }
    
    
    const dbItems = [{
      species: 'cat',
      name: 'Spot',
      strength: 20,
    }, {
      species: 'dog',
      name: 'Hazel',
      strength: 30,
    }, {
      name: 'test',
    }];
    const speciesList = dbItems.map(createSpecies);
    
    console.log({
      dbItems,
      animalClasses: speciesList.map(getClassName),
      animalValues: speciesList.map(species => species.valueOf()),
      animalJSONs: speciesList.map(species => JSON.stringify(species)),
    });
    const [ cat, dog ] = speciesList;
    
    cat.miau();
    dog.wuf();
    .as-console-wrapper { min-height: 100%!important; top: 0; }