Javascript way to design inheritance + privacy

211 views Asked by At

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 ?

2

There are 2 answers

0
Eric Elliott On BEST ANSWER

How to Safely Inherit Private Data in JavaScript

Often, people use underscores to denote that a property or method should be considered private. That's a bad idea.

Why Underscores Are a Bad Idea

Underscores do not guarantee data privacy, and there are a couple important problems that they create:

  1. Newbies don't know what underscores mean, so they ignore them.
  2. Advanced users think they know what they're doing, so the underscores don't apply to them.
  3. Implementation details may change, which may break the code the user code that used the underscore properties.

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.

Use Functional Inheritance for True Data Privacy

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:

0
Esailija On

I personally disagree with most things in that book and find them very poorly argumented. If not that, most of them are utterly irrelevant due to strict mode and modern tools like jshint. But I guess that is just a problem with how outdated the book is, since these were not available at the time.

The language's object model was extended in 2009 with plenty of new features and methods which made the difference between variables and properties even larger. If it was possible 10 years ago to pretend variables are properties, it certainly is not now.

The communicate that something is not part of published API, an underscore prefix is used like:

this._age = 5;
this._method();

This might have a special meaning to modern IDEs but it doesn't have any special meaning to reflective methods in the language like for..in and getOwnPropertyNames.

There are plenty of tutorials and guides on how to use the natural* constructor prototype pattern to express a concept of a class.

*The definition of natural I use means what the javascript engines recognize and optimize for.