Internal Classes In CoffeeScript

CoffeeScript provides us with a very nice syntax for creating classes.

class Dog
  constructor: (@name) ->

  bark: ->
    "Woof, #{@name}"

Here is the equivalent code in JavaScript:

function Dog(name) {
  this.name = name;
}

Dog.prototype.bark = function() {
  return "Woof, " + this.name;
};

We can use the same syntax for creating internal / private classes. (I don’t know if there’s a correct name for it, but I’m basically referring to a class which is defined within the parent class.)

class Dog
  class Bark
    constructor: (name) ->
      @name = name

    sound: ->
      "Woof, #{@name}"

  constructor: (name) ->
    @barkHelper = new Bark(name)

  bark: ->
    @barkHelper.sound()

dog = new Dog("Beethoven")
dog.bark()  # Woof, Beethoven

This works as you would except - we can access the internal / private class from within the parent class (Dog), but not from outside it.

bark = new Bark("Beethoven")  # ReferenceError: Bark is not defined

We can also expose this internal class to outside callers - we can effectively treat the parent class as a namespace.

class Dog
  class @Bark
    constructor: (name) ->
      @name = name

    sound: ->
      "Woof, #{@name}"

  constructor: (name) ->
    @barkHelper = new Dog.Bark(name)

  bark: ->
    @barkHelper.sound()

bark = new Dog.Bark("Beethoven")
bark.sound()  # Woof, Beethoven

I find this especially useful for testing. For example, you may find it necessary to extract some piece of functionality into an internal class, but if we didn’t expose that internal class in this way there would be no way to test it. It might seem messy to expose an internal member purely for testing, but I think this is a safe middle ground - it is still very obvious that the internal / private class should only be used in the context of the parent class.

Here is the equivalent code in JavaScript:

var Dog = (function() {
  Dog.Bark = (function() {
    function Bark(name) {
      this.name = name;
    }

    Bark.prototype.sound = function() {
      return "Woof, " + this.name;
    };

    return Bark;

  })();

  function Dog(name) {
    this.barkHelper = new Dog.Bark(name);
  }

  Dog.prototype.bark = function() {
    return this.barkHelper.sound();
  };

  return Dog;
})();

var bar = new Dog.Bark("Beethoven");
bark.sound();

Happy coding.