Against the "factory function" pattern08 April 2018
Unlike traditional object-oriented languages where normally only constructors can (and have to) be preceded by the
newd. When this happens, a new object is created and set as the binding (the value of
this) inside the function, the value of this object’s hidden
__proto__ property is set to the value of the
prototype property of the function instance, and finally this new object is by default returned to the caller even if there is no explicit
return this statement.
Alternatively, since ES2015:
The “factory function” pattern
this and prototypes in favor of a much more straightforward object factory function. Instead of
newing functions and setting properties as appropriate on the
this reference inside of the function body, a new object is explicitly created and returned.
The core arguments for the “factory function” are:
- It does not rely on function binding (
- It allows for procedural encapsulation
- It does not use those ugly prototypes
The core acknowledged disadvantage of this pattern is that is does not allow for inheritance, as such it is not considered an OOP pattern (though may be considered “object-based”.) This is, however, mostly not regarded as a big disadvantage, for one it is always possible to use composition instead of inheritance, and secondly inheritance is considered harmful as it is a form of very tight coupling (see also the “gorilla-banana” problem.) This means you can use this pattern for more or less anything you’d want to use classes for1.
The argument against factory functions
So, why would you want to use classes/constructors over this wonderful patter? We’ll address arguments number 1 and 2 above.
One of the main advantages of scripting languages is that you can easily and quickly write and run a script. The fact that function binding is a dynamic, runtime property is intended to be an advantage. It is true that, in retrospect, the highly volatile nature of binding is more of a disadvantage than an advantage as it represents a major source of confusion. The new anonymous function syntax introduced in ES2015 effectively removes binding from the equation in favor of relying instead on the more widely understood lexical scope, this addresses the vast majority of cases where binding becomes a source of confusion.
You are free to decide to adopt the factory function pattern if you believe the dynamic nature of
this is too hazardous a feature to use for your project, and it is unnecessary for you accomplish everything you want. Really, the vast majority of practical cases where the binding becomes lost have to do with passing or returning a method defined on your class as a value, which is simply solved by using an arrow function or the
The purpose of encapsulation
As far as encapsulation goes, an easy way to encapsulate methods is to rely on function closure when defining your class module. Encapsulating instance state is not as easy; there are ways to achieve it, but they’re not pretty2. State and behavior should be encapsulated, but the real question is what is the point of encapsulation? There are really two kinds of encapsulation.
- run-time encapsulation
- author-time encapsulation
The factory function pattern achieves the first (stronger) level of encapsulation, even if you can write JS that seems to access private properties, you will only end up with a
ReferenceError at runtime. State and behavior defined within a function body is scoped to that function only, so only what you explicitly expose will be accessible from the outside (see also modules.) The level of encapsulation normally achieved in languages such as C# is really the second. The moment you introduce a reflection API, you can always “attack” the privately held state of a class instance. This is the same level of encapsulation you achieve in a language such as Typescript where you introduce compile-time constraints to accessing private class properties, but everything is freely accessible at runtime.
Compare this with Typescript:
The question is what is the purpose of encapsulation? Are we doing it to hide implementation details to prevent misuse of our types, or are we doing it for security reasons? Doing it for the first reason makes immediate and obvious sense: we want to make sure our newest junior dev hire does not start calling the
_doRead method instead of the
read method when implementing some new feature. Secondly, if we are even the slight bit concerned about disclosing the secrets of our implementation to the world, minification and obfuscation do as good a job as we can reasonably ask for.
The difference between number 1 and number 2 above is, for the purpose of developing an application, academic. It does not make a concrete difference for the purpose of encapsulating details at author time.
In summary, one of the strongest reasons for using the “factory function” pattern is encapsulation. This is better achieved by using some form of author-type validation (such as using Typescript.)
- A. Bokhari, The Gorilla, Banana Problem, 2017.
- R. Chen, Factory Function Pattern In-Depth, 2016.
- A. Rauschmayer, Exploring ES6, Chapter 15.3. 2017.
- C. Salcescu, Class vs Factory function: exploring the way forward, 2018.
- K. Simpson, this & Object Prototypes, 2017.
- _, Scopes & Closures, 2017.
There are variations of this pattern where furthermore the object returned from the factory function is “frozen”, it becomes impossible to “replace” the values of its properties at runtime by using
Object.freeze(). See this “ice factory” pattern. ↩
Another way to encapsulate your private methods is by using function closure within the module defining your class and “manually” switching their runtime binding/context when you call them from inside your public methods via
Function.prototype.call. Again, not pretty, also does not address encapsulating instance state, only “static” state. ↩