2

I'm using ES6 classes inside my game. I have two questions:

Question 1) I'm instantiating two singleton classes for BattleMode and ExplorationMode and passing in an initial parameter type.

Even though I'm console.logging explorationMode (an instance of ExplorationMode), it shows Class BattleMode with type exploration. Why is this?

Question 2) I'm using gulp with babel to compile scripts into main.js. Both ExplorationMode and BattleMode above inherit from parent class Mode. Gulp runs through modes/ folder and adds them sequentially to main.js.

enter image description here

Apparently ES6 cares about the order of JavaScript, meaning if BattleMode is a child of Mode, then it will say it cannot find its parent.

Uncaught TypeError: Super expression must either be null or a function, not undefined

This forces renaming of mode.js such that it appears first in the folder order. That's pretty hacky. If I prepend A_mode, the error goes away.

enter image description here

Is there a better solution for this besides renaming or specifying each parent in gulp to appear before its children?

Otherwise, I could just use Objects:

var Mode = Mode || {};

Mode.BattleMode = Mode.BattleMode || {
    doThing : function () {
         ...

Question 1:

class StateManager {
    constructor(type) {
        if (!stateManagerInstance) {
            stateManagerInstance = this;
        }
        this.type = type;
        const battleMode = new BattleMode('battle');
        const explorationMode = new ExplorationMode('exploration');

        console.log('MODE: ', explorationMode); // Wrong
        ...

Console Output:

MODE: Class BattleMode { type: "exploration" , screenState: "battle" }

Mode:

/*
  Define as Singleton
 */
let modeInstance = null;

class Mode {
    constructor() {
        if (!modeInstance) {
            modeInstance = this;
        }

        return modeInstance;
  }

}

ExplorationMode:

/*
  Define as Singleton
 */
let explorationInstance = null;

class ExplorationMode extends Mode {
    constructor(type) {
        super(type);

    this.type = type;

        if (!explorationInstance) {
            explorationInstance = this;
        }

    this.screenState = '';

        return explorationInstance;
  }

BattleMode:

/*
  Define as Singleton
 */
let battleInstance = null;

class BattleMode extends Mode {
    constructor(type) {
        super(type);

    this.type = type;

        if (!battleInstance) {
            battleInstance = this;
        }

    this.screenState = '';

        return battleInstance;
  }

Question 2:

Gulp:

gulp.task('build-dev', function() {
    gulp.run('scripts');
  gulp.watch('js/**/*.js', function() {
    gulp.run('scripts');
  });
});

gulp.task('scripts', function() {
    return gulp.src([
            'js/**/*.js',
        ])
        .pipe(babel({
            presets: ['es2015']
        }))
        .pipe(concatJs('main.js'))
        .pipe(gulp.dest('build/js'));
});
user3871
  • 12,432
  • 33
  • 128
  • 268
  • Why is file system order determining the order your files are executed? That should not be the case. That is indeed hacky and your JS file execution order should have nothing to do with order in the file system. Perhaps you need a container file that imports files in the proper order and you only run the container file. – jfriend00 Mar 17 '18 at 21:28
  • 1
    Singletons are an invention of people that do not know objects. – Jonas Wilms Mar 17 '18 at 21:35
  • @JonasW. I like that creational design pattern `:)` further, there are use cases. [Singleton pattern](https://en.wikipedia.org/wiki/Singleton_pattern) – Ele Mar 17 '18 at 21:38
  • 1
    "*I'm instantiating two singleton classes*" - [there's your first mistake](https://stackoverflow.com/a/38741262/1048572). – Bergi Mar 17 '18 at 21:39
  • 1
    @Ele The uses cases are mostly restricted to languages that have nothing but `class` constructs. JS is not one of those languages. – Bergi Mar 17 '18 at 21:40
  • @Bergi perfect, but that doesn't change anything about my statement. – Ele Mar 17 '18 at 21:42
  • 2
    "*Is there a better solution for this besides renaming?*" - yes, of course. Always explicitly declare your dependencies between modules using `import`. – Bergi Mar 17 '18 at 21:42
  • @bergi thanks. To be clear, es6 "new" is appropriate for everything but singletons. – user3871 Mar 18 '18 at 00:34

1 Answers1

1

This is an example why singleton can be antipattern. It provides extra complexity without real benefits. Children classes don't even benefit from inheritance.

Since both BattleMode and ExplorationMode inherit from Mode, Mode constructor is evaluated first. The first instance is saved to modeInstance. Since BattleMode is instantiated first, it's an instance of BattleMode, no matter what happens in child constructors.

In this case and many others singleton classes should just be replaced with plain objects.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565