Private Methods

# Kevin Smith (13 years ago)

(I seem to have some trouble sending this one through - let's see if it works this time...)

Let's say that we have a class with two methods, and we divide the body of each method into abstract "regions":

M1() {
  <A>
  <B>
  <C>
}

M2() {
  <D>
  <C>
  <E>
}

Both M1 and M2 share the region <C>. In a traditional class-based

language, we would refactor <C> into an abstract operation using a private

method:

M1() {
  <A>
  <B>
  this.M3();
}

M2() {
  <D>
  this.M3();
  <E>
}

private M3() {
  <C>
}

In a traditional class-based language, the refactored code will:

  1. provide an identical public interface, and
  2. be functionally identical

These two guarantees allow us to separate interface from implementation, and to localize the implementation strategy.

Will private methods, implemented with private name objects, provide us with the same guarantees? Let's refactor again, this time using a private name for the new method:

// Assume that there exists a "const M3 = Name.create();" somewhere

above

M1() {
  <A>
  <B>
  this[M3]();
}

M2() {
  <D>
  this[M3]();
  <E>
}

M3 { <C> }

Since M3 is not visible to any code that does not also have a reference to the M3 private name, we can say that this refactoring provides the same public interface. But is it functionally identical?

Let's assume that prior to the refactoring, M1 and M2 were generic methods. That is, they reference only properties of |this| which correspond to some subset S of the public interface of the class. Any other object which implements S can also be used with M1 and M2 (like the generic methods defined on Array.prototype).

After the refactoring shown above, there's no possibility that M1 and M2 can be generic, since they access a privately named property of |this| which by definition is not a part of the public interface of the class. So in this sense, the refactored code is not functionally identical to the original code.

If we wanted to preserve identical functionality, we could define M3 as a regular function and call it using the call method of Function.prototype:

// Somewhere *before* the class definition we have:
function M3() {
  <C>
}

...

M1() {
  <A>
  <B>
  M3.call(this);
}

M2() {
  <D>
  M3.call(this);
  <E>
}

The above strategy works, but (a) explicitly calling M3 via call() is awkward, and (b) it defeats the purpose of an elegant class syntax if we must refactor code into functions outside of the class definition.