I made a package - private properties and strict typing

This is my first foray into Node but I wanted to do some groundwork before building some more complex packages.

One of the biggest problems with Javascript is the lack of visibility. This is particularly problematic now we have easily accessible and usable libraries and packages. The problem is that it’s impossible for package authors to prevent backwards compatibility breaks when refactoring.

For example let’s say I have a class:


class MyClass {
  calculateTax(value) { 
         return  value * this.rate;
   }

  getTotal() {
        return this.total + this.getTax(this.total);
   } 
}

If I refactor this into:


class MyClass {
  constructor(taxCalculator) { 
          this.taxCalculator = taxCalculator;
   }

  getTotal() {
        return this.total + this.taxCalculator.calculateTax(this.total);
   } 
}

I cannot publish the package in the knowledge that it won’t break someone else’s code. They may have been using the MyClass.calculateTax function somewhere in their project. My refactor will now break their code when they update my package. Probably not what I intended! By following OOP standards and making properties/methods private when you don’t want third parties to call them you can can prevent this problem: Private methods can be removed, have their arguments changed or be renamed safe in the knowledge that the only place it’s called from is the same class. If a method cannot be called externally there’s no way to break someone else’s code when refactoring as long as the public methods have the same API.

To solve this I’ve implemented private properties and strict typing in Javascript:

It can be used like this:


var StrictOOP = require('strict-oop');

//Firstly declare the class as normal
class Car {
	constructor() {
		this.speed = 0;
	}

	accellerate() {
		this.speed += 5;
	}

}

var car = new Car();

//Add strict OOP rules to the `car` object
var oop = new StrictOOP(car);

//set the speed property to private
oop.property('speed').is('private');

//Now, trying to set it will cause an error to be thrown
car.speed = 100;

//This will update the speed as expected because the accellerate method is inside the class
car.accellerate();

The example above can also be expressed as:


var StrictOOP = require('strict-oop');

//Firstly declare the class as normal
class Car {
	constructor() {
		//Add strict OOP rules to this instance
		var oop = new StrictOOP(this);
		oop.property('speed').is('private');

		this.speed = 0;
	}

	accellerate() {
		this.speed += 5;
	}

}

//Create an instance of `Car` as normal:
var car = new Car();

//But trying to set it will cause an error to be thrown
car.speed = 100;

//This will update the speed as expected because the accellerate method is inside the class
car.accellerate();

This approach gives class authors the ability to define whether they want to enforce strict OOP in their classes.

oop.property(name) can be used to select a property on any object (including this and set its visibility using oop.property(name).is('private') or force it to only allow certain types using oop.property(name).type('someType')

The package is lightweight (~100 lines) and unit tests are provided :slight_smile:

Comments/suggestions are welcome, the next release will also have argument type checking using oop.method(methodName).arguments(['string', ' number, 'MyClass', 'any']);

1 Like

why not using a factory function and omit the haggly class/constructor issues altogether?

This was just an example, but yes let’s say I refactor the calculateTax function into a factory or in some other manner move it outside MyClass - Same problem: someone might be calling the function somewhere in their code. They upgrade my package and their code stops working.

if you intend your code for Node, that’s not a problem since Node’s require() gives you a unique namespace.

Perhaps I didn’t illustrate the problem correctly.

My package contains the class MyClass:

class MyClass {
  calculateTax(value) { 
         return  value * this.rate;
   }

  getTotal() {
        return this.total + this.getTax(this.total);
   } 
}

Someone downloads and uses my package:


const MyClass = require('my-class');

var myobj = new MyClass();

var tax = myobj.calculateTax(12345);

If my package is now refactored so that MyClass no longer contains the calcualteTax function, the code above that was working perfectly, stops if the package that contains MyClass is upgraded.

As the developer of the class MyClass there’s no way for me to know whether removing the function will affect someone else’s code or not. If it’s private I know that nobody else is relying on the function and I can mess around with it as much as I like and change the return type, arguments or remove the function entirely.

The user of the package either has to rewrite parts of their code or stay with the outdated version of the package. Neither is ideal. By enforcing visibility on methods it gives package authors more control over the class API and ability to prevent backwards compatibility breaks because they know which methods can be change and which cannot.

I have to admit that I’m not overly convinced of ES6 “classes” anyway. :-D Why not simply use an old-fashioned cunstructor function which only returns a public API in the first place, like

var MyClass = function() {
    var total, rate;

    var _calculateTax = function(value) { 
        return  value * rate;
    };

    var _getTotal = function() {
        return total + _calculateTax(total);
    };

    return {
        getTotal: _getTotal
    };
};

var myClass = new MyClass();
// ...

IMHO it’s rather telling that you need to write some kind of workaround to make class properties private. Sounds like a very interesting project nevertheless, I’m having a look at GitHub now! :-)

1 Like

IIRC that’s called the Revealing Module pattern.

1 Like

I thought that would be if the module is invoked immediately like

var myModule = (function() {
    // etc...

    return {
        something: _something
    };
})();

myModule.something();

… i.e. not callable with the new keyword. Could be wrong though, I’d have to check TBH.

that may be, though when you don’t invoke it immediately you have a factory which serves the same purpose as a class.

in JavaScript you should never use new.

Maybe, kind of. But to implement a factory on which you’d then call a method to return a particular type of object would only make sense if the creation of that object requires some further logic you’d want to abstract away. – Otherwise, you could just as well use the new keyword. Having such a generic interface for every ever so simple object seems overly complicated to me anyway.

Could you elaborate on that?

(BTW, it just occurred to me that this thread might not entirely be about modules. Otherwise, you could extend objects via their prototypes to separate public from private methods, of course.)

Building some sort of class solution is a bit of a rite of passage in JavaScript, so congratulations. :slight_smile: And your solution is indeed clever, getting a stack trace to check who the caller is.

If you’re going to build more complex packages, though, it’s probably important to be aware of and strongly consider the alternatives before moving ahead with a homebew solution. (You said this is your first foray, so I genuinely don’t know if you’re already aware of these alternatives or not.)

By convention

There is of course always the leading underscore convention. It’s not enforced by the language, but programmers know not to use or rely on an underscored property. It may seem crude, and it is, but nonetheless it works.

By closures with weak maps, or symbols

When you get down to it, every solution that achieves true privacy in JavaScript uses closures at some point. In your solution, for example, the replacement methods and property getters/setters have closured access the original methods and original properties. Alternatively, the class methods could have closured (that is, private) access to a map of private values, or to a private key.

const MyClass = (function() {
    // Private speed values, mapped by the owning class instance
    const speed = new WeakMap()

    // Alternatively, a private speed key
    const speedKey = Symbol()

    return class {
        constructor() {
            // Privacy with weak maps
            speed.set(this, 0)

            // Privacy with symbols
            this[speedKey] = 0
        }
    }
}())

TypeScript

And of course there’s TypeScript. It’s a superset of JavaScript, so it’s ordinary JavaScript but with visibility and type checking and some other things sprinkled in.

class Animal {
    private name: string;
    constructor(theName: string) { this.name = theName; }
}

new Animal("Cat").name; // Error: 'name' is private;

The good and bad part is that all checking is done at compile time rather than runtime. That’s partly good because it doesn’t cost you any overhead, but also partly bad because users won’t get visibility and type checking unless they also use TypeScript.

4 Likes

just a short list:

https://medium.com/javascript-scene/common-misconceptions-about-inheritance-in-javascript-d5d9bab29b0a
https://medium.com/javascript-scene/the-two-pillars-of-javascript-ee6f3281e7f3

Thanks, I’m aware of Typescript, I haven’t used it for anything yet but I couldn’t find a simple way of using it with Electron and ran into several issues and lack of examples (It looks like it’s possible: https://discuss.atom.io/t/is-there-a-good-quickstart-example-out-there-of-using-typescript-with-electron/28917 – this was posted after I started playing with my own solution). My knowledge of both Typescript and Electron isn’t great I just wanted a working solution without the setup headache.

Closures work, but they don’t do type checking and the code isn’t particularly clean. If I’m adding additional code to each method for type checking I may as well abstract it like I did here :slight_smile: Type checking is particularly nice for refactoring. If I change a method’s arguments I can run the tests and very quickly get a list of all the calls I need to update.

Elliott first. The short version is that most everything Eric Elliott has ever claimed about inheritance and composition is flat wrong.

  • He’s claimed his style of “composition” is immune to the fragile base problem. He was wrong.
  • He’s claimed his style of “composition” is immune to the diamond problem. He was wrong.
  • He’s claimed his style of “composition” is immune to deep hierarchies. He was wrong.
  • He’s claimed his style of “composition” has no hierarchy at all. He was wrong.
  • Bizarrely, Elliott even claims that class A extends B {}, even in a language like Java or C++, is not classical inheritance. It’s sad that I have to explicitly rebut this, but he was wrong.

And not only has Elliott invented his own personal and bizarre definition of classical inheritance, but he’s also invented his own personal definition of “composition” (that’s why above I always put the word in quotes).

In reality, composition means containing an object reference as a value in another object’s attributes. This is the nearly universally accepted meaning of composition, especially when we’re talking in the context of vs inheritance. On MDN, for example, composition is described as letting class instances be the values of other objects’ attributes. A description that exactly matches this Python article on composition vs inheritance. Which also exactly matches this ActionScript article on composition. Which also exactly matches this C++ article on composition vs inheritance. Which also exactly matches the Wikipedia entry for object composition. Which also exactly matches the GoF Design Patterns description of object composition (“objects acquiring references to other objects”).

Whatever you think you learned from Eric Elliott is almost certainly very wrong.

Second, Kyle Simpson. His argument is at least much more accurate and respectable. His argument is that classes in JavaScript obscure the fact that the implementation under the hood is different than, say, Java. And he’s right.

But, the world of programming languages is bigger than just Java, and we need to break free from thinking that Java’s implementation of classes is the only possible implementation. In Python, for example, classes are themselves real living objects – for example, we can monkey-patch them at runtime. And inheritance from instance to class, and from class to superclass, happens at runtime by delegation… just like in JavaScript. And there are still other comparable class implementations, such as in Ruby and Smalltalk. Java doesn’t own the concept of a class.

Further, though It’s of course true that JavaScript’s classes, under the hood, are not the same as Java’s classes, it’s also true that JavaScript’s functions, under the hood, are not the same as Java’s functions. Why is it okay that functions behave differently compared to Java but not okay that classes behave differently? Why does any comparison to Java even matter?

6 Likes

I don’t get it.

This just seems much more complicated than just using an oo language.

if you’re going to use JavaScript than shouldn’t you work for it not against it.

Having to use Typescript and what ever else just to replicate oo when you could just use an oo language on the server in the first place. It just seems like such a complicated mess.

All these dependencies just for basic language features that any traditional oo language provides. Jeesh…

I should probably have put this in the OP but this was actually for an Electron app. There is a lot of visual power available in HTML/CSS that is difficult to replicate with C#, Java, etc without considerable work.

There is a way to do private variables, that @Jeff_Mott pointed out earlier.

Another option is to use the Closure Compiler and annotations. I know you probably hate that word, but nonetheless here’s what that would look like:

// Animal.js
class Animal {
    constructor(theName) {
      /**
       * @private
       */
      this.name = theName;
    }
}

// other-file.js
new Animal("Cat").name; // JSC_BAD_PRIVATE_PROPERTY_ACCESS: Access to private property name of Animal not allowed here.

You can also define and check types with these. Here’s the list of annotations you could use, https://developers.google.com/closure/compiler/docs/js-for-compiler#tags

You wouldn’t even need to use the minified output of the Closure Compiler, but rather just treat it as a linter with extra features.

Actually because these are genuinely part of the class API annotations make sense here, however if it has to be complied to javascript then there’s not really any difference from typescript (if I can get it working with Electron)

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.