1
\$\begingroup\$

I've been trying to find a way to create static properties in ES6 classes and have found a solution with the help of various Stack Overflow answers (namely this one and this one).

However, I also wanted to take this a step further and make the static property an array that can be updated/added to using a special static method. I've managed to come up with something that works. Here is a complete demonstration using the class Apple and static property possibleColors:

    class Apple
    {
      constructor(args = {})
      {
        this.color = args.color || 'red';
        this.description = args.description || 'An apple.';
      }
      
      static get possibleColors()
      {
        if (!this._possibleColors)
          this._possibleColors = ['red', 'green', 'yellow'];
        return this._possibleColors;
      }

      static set possibleColors(arr)
      {
        if (Array.isArray(arr))
          this._possibleColors = arr;
        else
          console.log("Possible colors must be an array.")
      }

      static addPossibleColor(c)
      {
        if (!this._possibleColors)
          this._possibleColors = this.possibleColors;
        this._possibleColors.push(c);
      }

      get color()
      {
        if (!this._color)
          this._color = '';
        return this._color;
      }

      set color(c)
      {
        if (Apple.possibleColors.includes(c))
          this._color = c;
        else
        {
          console.log(`Color '${c}' is not a valid apple color.`);
          if (!this._color)
            this._color = 'red';
        }
      }
    }

The possibleColors property belongs to the class only and will not be a part of new instances. I've also taken measures to ensure that its value is always an array. It defaults to red, green, & yellow, but you can view/change possible colors using the regular getter/setter:

Apple.possibleColors = ['color', 'othercolor'] 

You can also add new possible colors like this:

Apple.addPossibleColor('newcolor')

In this example, the whole point of having the possibleColors static property is to have a definitive list of valid colors stored in the main class and prevent any Apple instances from being set to a color not in the array. Of course, I can think of other use cases for this kind of technique.

For example, I'm currently trying to write my own basic component system that uses templates to draw/render each component as HTML on the page. The main class (Component or similar) would have a static property for storing the templates in an array, and you'd choose a template from this array when instantiating a component (or it would choose a default if none specified).

What I'd like to know:

  1. Could this approach cause any problems I haven't foreseen (either in this specific scenario or a more complex one)?

  2. Are there better ways to do anything I've demonstrated here?

  3. Is the way I'm handling static properties overall considered good practice?

\$\endgroup\$
0
\$\begingroup\$

It is a bad idea to change values in in a static context, static basically means that it doesn't change anything

static get possibleColors(){
    if (!this._possibleColors){
        this._possibleColors = ['red', 'green', 'yellow'];
    }
    return this._possibleColors;
}

You would want to change this value elsewhere. Maybe getting the colors from localStorage or a database etc. Lets do it with localStorage


I am using CamelCase and Java syntax, then update where it could be improved

class Apple{
    //If the values are undefined, then it will default to these values
    constructor({color = "red", description = "An apple"}){ 
        this._color = color;
        this._description = description;
    }

    //getters and setters

    get color(){
        return this._color; // Always has a colour
    }

    set color(color){ //Don't shorten parameters
        if (Apple.possibleColors.includes(color)){
            this._color = color;
        }else{
          console.log(`Color '${c}' is not a valid apple color.`);
          //Don't set the color if it isn't applicable
        }
    }

    //added
    get description(){
        return this._description;
    }

    //Static methods

    static get possibleColors(){
        const possibleColors = localStorage.getItem('possibleColors');
        if(possibleColors){
            return JSON.parse(possibleColors);
        }
        return ['red', 'green', 'yellow'];
    }

    static set possibleColors(colorArray){
        if (Array.isArray(colorArray)){
            localStorage.setItem('possibleColors', JSON.stringify(colorArray));
        } else {
            console.log("Possible colors must be an array.");
        }
    }

    static addPossibleColor(color){
        const possibleColors = Apple.possibleColors;
        possibleColors.push(color);
        Apple.possibleColors = possibleColors;
    }
}

Initiating the class is the same

const greenApple = new Apple({color:"green"});
console.log(greenApple.color); //green
console.log(greenApple.description); //An apple

Notice that the getters and setters come before the static methods. Generally they come first, then other methods come afterwards.

Also you should probably have the static methods in another class, because Apple is feels like a model object that just contains data.

this.color -> this._color otherwise it will collide with get/set, you can see in your code that your get thinks the color is undefined first time get is called

\$\endgroup\$
  • \$\begingroup\$ Thanks for the suggestions. The localStorage idea is very interesting. It sounds like what I'm trying to accomplish would best be done outside the class after all. "It is a bad idea to change values in in a static context, static basically means that it doesn't change anything." I do have to ask: what, then, is the point of even having static getters and setters in the language? \$\endgroup\$ – ReeseyCup Feb 6 '18 at 2:40
  • \$\begingroup\$ I've never seen static getters and setters. Static is usually just a method that doesn't depend on anything from the outside, and it can't change anything on the outside either. JavaScript is a bit lala in this area, but if you use a strongly typed language, then you will see the linter going crazy \$\endgroup\$ – Pavlo Feb 6 '18 at 3:07
1
\$\begingroup\$

Protected states require enforcement..

Why would you use the possible colors and complicated apple color vetting process when there is no requirement in your code to do so?

You are using a static list (array) in an effort to protect the state of the Apple, or associated states that rely on apples being specific colors.

Maintaining trust in object states is important and allows you to make assumptions and reduce the code complexity. It is also one of the most important methods coders have of controlling our arch nemeses.. The Bug!

But a chain is only as strong as its weakest link. Even if you put a big sign above it saying "Don't yank this pitifully weak link!" you can't trust coders, not even yourself, for one day the convenience is too tempting, and while in a rush to meet a dead line you yank that chain.

const myApple = new Apple();  // Needs to be "Reddish" not "red"
myApple._color = "Reddish";   // Done, so much easier than wrangling the 
                              // possibleColors static and its dificult siblings

And just like that all the work put into protecting the state of your apple is out the window.

Coders are lazy they will not put up with complications if there is an easy way. Coders want performant code and they are not going to use getters and setters when there is direct access. Putting a sign up "Do not use" (_ as the metaphoric sign "private") does not stop use.

If you want to control the color of the apple you must make it impossible to change it without the vetting process, or it is pointless, and pointless code is bad code.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the insight. It's a good point that you can "cheat" by setting the actual property directly. Is there any way you can suggest to enforce this kind of protection? From what I understand, immutability isn't really possible in JavaScript without a library. As I mentioned to @Pavlo, it makes me wonder why static setters are in the language to begin with. \$\endgroup\$ – ReeseyCup Feb 6 '18 at 2:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.