-1

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);
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
kosta
  • 3
  • 2
  • 2
    Please post a [mcve] in your question. Don't link to external code. – jabaa Aug 19 '22 at 20:04
  • You have to store the type in the database. You could either use different tables for different types or use a column. – jabaa Aug 19 '22 at 20:08
  • If I store type or class name as variable in database, I would need to manually make ifs for each case. if(class === 'dog') new Dog(...) and so on. – kosta Aug 19 '22 at 20:12
  • Yes, you have to do something like this. – jabaa Aug 19 '22 at 20:13
  • is there any better way to handle this (something more elegant and automatic) because if I have 100 classes things will get messy – kosta Aug 19 '22 at 20:21
  • Can you describe a real world use-case where the client has to load data, but doesn't know the type and meaning of this data? How would the client use this data? Usually the client wants to either load a cat or a dog and know that the cat data is in the cat table and dog data is in the dog table. – jabaa Aug 19 '22 at 20:23
  • In my case user/player have Inventory of items. Items can be different type (food, drinks, weapons, tools ..) and logic, but they hold the same data (name, amount ...). So in this case database only store item data but on load this data need to create corresponding classes so proper methods can be called and static data used. – kosta Aug 19 '22 at 20:30
  • I would use a field containing the type. – jabaa Aug 19 '22 at 20:34
  • Please provide enough code so others can better understand or reproduce the problem. – Community Aug 20 '22 at 00:22
  • You could create a `Factory` class, that checks the type and then returns the proper instance. For example `let inventoryItem = myFactory.create(data.type)`. Yes you will need lots of `if` or a `switch` statement in the factory class. – Kokodoko Aug 20 '22 at 21:26
  • "but they hold the same data " - what you are trying to do is an anti-pattern in object -oriented programming. If objects hold the same data, you shouldn't create separate classes for them. – Konrad Aug 20 '22 at 23:41
  • @Kokodoko I want to avoid the whole if/switch code. If an item has many methods inside I would need to check what is item type for each one... That's why I want design where each item type has its own class. Then item classes can have unique methods or just overwrite methods from the base item class. – kosta Aug 21 '22 at 14:50
  • @KonradLinkowski yes they hold the same data like id, name, and amount but there is also some data that is unique to each item type (class). There is also some static data thats defined on class creation and its different for each type. – kosta Aug 21 '22 at 14:52
  • Maybe this post can help you. They put the classnames in an object, so you can reference them as a string and create the instance. https://stackoverflow.com/questions/34655616/create-an-instance-of-a-class-in-es6-with-a-dynamic-name – Kokodoko Aug 21 '22 at 21:14
  • @kosta … Regarding the so far provided sole answer / approach are there any questions left? – Peter Seliger Sep 27 '22 at 10:37

1 Answers1

0

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; }
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37