DEV Community

Cover image for Dynamic ES6 class instantiation using proxy classes
Jeff Caldwell
Jeff Caldwell

Posted on

Dynamic ES6 class instantiation using proxy classes

I'm working on a project that lets users create different types of broadcast graphics for their live streams. This is a great place to use classes because I can have a base Graphic class and extend that for the different kinds of graphic that will play out onscreen.

This can get a little difficult to deal with later when it comes time to instantiate one of ten or twenty graphic subclasses. Nobody wants to have to have to try using logical if/else statements, or even switch statements to sift through every kind of subclass.

Enter proxy classes.

Sticking with the example above, let's say we have a base Graphic class

class Graphic {
    _id = Date.now();
    position = [2,2];
    autoplay = false;
    entranceAnimation = {type: 'fade', direction: 'none', duration: 500, delay: 0};
    exitanimation = {type: 'fade', direction: 'none', duration: 500, delay: 0};
    isPlaying = false;

    constructor(options) {
        this.showName = options.showName;
        this.scene = options.scene;
    }

    play() {
        if(this.isPlaying === false) {
         this.isPlaying = true;
        }
    }

    stop() {
        if(this.isPlaying === true) {
            this.isPlaying = false;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, this base class is just here so we can have some nice defaults for all of the graphic subclasses, so let's make a subclass called Bug (a bug is just a very simple graphic that shows in one of the top corners).

class Bug extends Graphic {
    type = 'bug';
    position = [1,1];
    entranceAnimation = {type: 'fly', direction: 'left', duration: 500, delay: 500}
    exitAnimation = {type: 'fly', direction: 'left', duration: 500, delay: 0}

    constructor(options) {
        super(options);

        this.content = options.content;
    }
}
Enter fullscreen mode Exit fullscreen mode

Cool, cool. Now, we can instantiate a bug.

const myFirstBug = new Bug({content: 'LIVE'});
console.log(myFirstBug);
Enter fullscreen mode Exit fullscreen mode
Bug {
  _id: 1602690699857,
  position: [ 1, 1 ],
  autoplay: false,
  entranceAnimation: { type: 'fly', direction: 'left', duration: 500, delay: 500 },
  exitanimation: { type: 'fade', direction: 'none', duration: 500, delay: 0 },
  isPlaying: false,
  showName: undefined,
  scene: undefined,
  type: 'bug',
  exitAnimation: { type: 'fly', direction: 'left', duration: 500, delay: 0 },
  content: 'LIVE'
}
Enter fullscreen mode Exit fullscreen mode

This is what we want. A base class with reasonable default fields that we can inherit from or override when we need to. The basics are working as they should.

Now let's create another type of graphic, LowerThird (a graphic that plays on the lower part of the screen, usually with a speaker's name and title or details about what's on screen).

class LowerThird extends Graphic {
    position = [3,1];
    entranceAnimation = {type: 'scale', direction: 'left', duration: 700, delay: 0};
    exitAnimation = {type: 'scale', direction: 'left', duration: 700, delay: 0};

    constructor(options) {
        super(options);

        this.subjects = options.subjects;
    }
}
Enter fullscreen mode Exit fullscreen mode

And the instantiated LowerThird:

const myLowerThird = new LowerThird(
    {
        subjects: [
            {
                title: 'John Brown',
                info: 'Radical Abolitionist'
            },
            {
                title: 'James Baldwin',
                info: 'Writer, Expatriot'
            }
        ]
    });

console.log(myLowerThird);
Enter fullscreen mode Exit fullscreen mode
LowerThird {
  _id: 1602690699917,
  position: [ 3, 1 ],
  autoplay: false,
  entranceAnimation: { type: 'scale', direction: 'left', duration: 700, delay: 0 },
  exitanimation: { type: 'fade', direction: 'none', duration: 500, delay: 0 },
  isPlaying: false,
  showName: undefined,
  scene: undefined,
  exitAnimation: { type: 'scale', direction: 'left', duration: 700, delay: 0 },
  subjects: [
    { title: 'John Brown', info: 'Radical Abolitionist' },
    { title: 'James Baldwin', info: 'Writer, Expatriot' }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Simple stuff, but we're not here to watch someone create and instantiate classes all day. We're here to see why proxy classes can help.

I don't want to have to call new LowerThird() or new Bug() or new HundredthGraphicType() when someone creates a new graphic. I want to be able to allow my program to decide what subclass of graphic needs to be instantiated at runtime and just instantiate it for me. You can see a breakdown of proxy classes in this StackOverflow answer.

It's a pretty simple pattern - create a class whose constructor parameters are the name of the class you want to make and any options you want to pass to that classe's constructor. Then, in the ProxyClass constructor block you just use the return statement to construct your new class.

Here, look:

class ProxyGraphic {
    constructor(className, options) {
        const graphicClasses = {
            Bug,
            LowerThird
        };

        return new graphicClasses[className](options);
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, instead of instantiating a new class directly you can just pass all of that to the proxy class and it will instantiate the correct kind of graphic for you (as long as you have the class referenced in the graphicClasses object. This can be really helpful when you want to create different kinds of classes depending on what users are selecting.

Here's everything together:

class Bug extends Graphic {
    type = 'bug';
    position = [1,1];
    entranceAnimation = {type: 'fly', direction: 'left', duration: 500, delay: 500}
    exitAnimation = {type: 'fly', direction: 'left', duration: 500, delay: 0}

    constructor(options) {
        super(options);

        this.content = options.content;
    }
}

class LowerThird extends Graphic {
    position = [3,1];
    entranceAnimation = {type: 'scale', direction: 'left', duration: 700, delay: 0};
    exitAnimation = {type: 'scale', direction: 'left', duration: 700, delay: 0};

    constructor(options) {
        super(options);

        this.subjects = options.subjects;
    }
}

class ProxyGraphic {
    constructor(className, options) {
        const graphicClasses = {
            Bug,
            LowerThird
        };

        return new graphicClasses[className](options);
    }
}

new ProxyGraphic('Bug', {content: 'LIVE'});
Enter fullscreen mode Exit fullscreen mode

Returns:

Bug {
  _id: 1602690769341,
  position: [ 1, 1 ],
  autoplay: false,
  entranceAnimation: { type: 'fly', direction: 'left', duration: 500, delay: 500 },
  exitanimation: { type: 'fade', direction: 'none', duration: 500, delay: 0 },
  isPlaying: false,
  showName: undefined,
  scene: undefined,
  type: 'bug',
  exitAnimation: { type: 'fly', direction: 'left', duration: 500, delay: 0 },
  content: 'LIVE'
}
Enter fullscreen mode Exit fullscreen mode

Okay, fair enough. But the whole point is to allow for greater flexibility in our programs by passing in whatever dynamic content we want. Let's simulate that by creating a couple of variables that we'll pretend are hooked up to some input fields on our page:

let userSelectedGraphic = 'LowerThird';
let userInputOptions = {subjects: [{title: 'Billy Batson', info: 'Average Kid'}, {title: 'Clark Kent', info: 'Mild Mannered Reporter'}]};

new ProxyGraphic(userSelectedGraphic, userInputOptions);
Enter fullscreen mode Exit fullscreen mode

Returns:

LowerThird {
  _id: 1602691813627,
  position: [ 3, 1 ],
  autoplay: false,
  entranceAnimation: { type: 'scale', direction: 'left', duration: 700, delay: 0 },
  exitanimation: { type: 'fade', direction: 'none', duration: 500, delay: 0 },
  isPlaying: false,
  showName: undefined,
  scene: undefined,
  exitAnimation: { type: 'scale', direction: 'left', duration: 700, delay: 0 },
  subjects: [
    { title: 'Billy Batson', info: 'Average Kid' },
    { title: 'Clark Kent', info: 'Mild Mannered Reporter' }
  ]
}
Enter fullscreen mode Exit fullscreen mode

That's it! I found this pretty useful and think it's a pattern that can make using classes a lot easier.

Top comments (0)