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;
}
}
}
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;
}
}
Cool, cool. Now, we can instantiate a bug.
const myFirstBug = new Bug({content: 'LIVE'});
console.log(myFirstBug);
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'
}
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;
}
}
And the instantiated LowerThird:
const myLowerThird = new LowerThird(
{
subjects: [
{
title: 'John Brown',
info: 'Radical Abolitionist'
},
{
title: 'James Baldwin',
info: 'Writer, Expatriot'
}
]
});
console.log(myLowerThird);
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' }
]
}
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);
}
}
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'});
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'
}
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);
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' }
]
}
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)