I read the book "JavaScript : The Good Parts" book by Douglas Crockford and so many other resources and i'm a bit confused about implementing inheritance AND privacy in Javascript.
I'm coming from Java World, I've understood that I can simulate privacy via closure, or doing some inheritance via prototype but I want to do it in a javascript way.
I know I can perform some inheritance with the prototypal / parasitic pattern. This is good for performance but there is no way to use some privacy members properly (without creating some closure function each time a new object is instanciated)
I know I can inherit from object and use privacy members via the functionnal / parasitic pattern such as suggested by douglas Crockford but there is an obvious performance / memory issue since functions will be created again each time an object is instanciated.
Finally I'm wondering if good practices in other language such as privacy encapsulation make sense in JavaScript. I've seen some post here where people said "we don't care about privacy, just tell the world that this property should not be accessed from the outside and that's enough".
Should I consider that good practices in Javascript is reduced to prototypal / parasitic inheritance, with public interface and hope that developpers will use the library as expected ? Or maybe thinking in terms of inheritance and encapsulation is a "java" way to think and not a javascript one ? How to use the power of duck programming in javascript to achieve these goals ?
Often, people use underscores to denote that a property or method should be considered private. That's a bad idea.
Underscores do not guarantee data privacy, and there are a couple important problems that they create:
These are problems because encapsulation is an important feature of object-oriented design. Objects exist to solve a particular problem. Private methods may solve problems which are only related as an implementation detail. Implementation details are more likely to change than public interfaces, so code that relies on implementation details may break when implementation details change.
Exposing only your public interface hides implementation details which may change, which guides users to rely on supported features, rather than unsupported implementation details.
Functional inheritance can be employed to inherit private data.
Functional inheritance is the process of inheriting features by applying an object augmenting function to an object instance. The function supplies a closure scope, which has the effect of hiding private data inside the function's closure.
Douglas Crockford coined the term in "JavaScript: The Good Parts". In Crockford's example, a child factory knows how to instantiate an object from an existing base factory, which has the effect of creating an inheritance hierarchy. However, that's a bad idea. We should always favor object composition over class inheritance.
You can create and compose functional mixins by modifying the pattern slightly to take the base object as a parameter.
The functional mixin enhances a supplied object instance. The function's closure scope may contain private methods and data. You may then expose privileged methods inside that function. It works like this:
const withFlying = instance => {
let canFly = true; // private data
let isFlying = false;
// Privileged method
instance.fly = () => {
isFlying = canFly ? true : isFlying;
return instance;
};
// Privileged method
instance.land = () => {
isFlying = false;
return instance;
}
// Privileged method
instance.getFlightStatus = () => isFlying ? 'Flying' : 'Not flying';
return instance;
};
// Create a new object and mix in flight capability:
const bird = withFlying({});
console.log(bird.fly().getFlightStatus()); // true
bird.land();
console.log(bird.getFlightStatus()); // false
Functional mixins can be composed together using standard function composition with any other base object and any other set of features. First, you'll need a compose function. You can use compose()
from Lodash, Ramda, or any functional programming library that provides a standard compose function -- or just write your own:
// Function composition: Function applied to the result of another function application, e.g., f(g(x))
// compose(...fns: [...Function]) => Function
const compose = (...fns) => x => fns.reduceRight((acc, fn) => fn(acc), x);
Now you can compose any number of mixins together using standard function composition:
// This function returns a function which can be used
// as a functional mixin.
// `text` here is private data that determines the sound
// `quack()` will log to the console.
const withQuacking = text => instance => {
// Privileged method
instance.quack = () => console.log(text);
return instance;
};
// Compose mixins:
// ('Quack!' is private data)
const createDuck = compose(withFlying, withQuacking('Quack!'));
const malard = createDuck({});
console.log(malard.fly().getFlightStatus()); // Flying
malard.quack(); // "Quack!"
For more versatile ways to compose factory functions using a variety of inheritance techniques, see the Stamp Specification.
References: