Mike's corner of the web.

Object orientation without classes

Saturday 9 January 2010 21:47

So, in the last post, I decided that object orientation was about a couple of key concepts: encapsulation and polymorphism. Revisiting our NameGenerator example, we had this code in Java:

public class NameGenerator {
    private final FirstNameGenerator firstNameGenerator;
    private final LastNameGenerator lastNameGenerator;
    
    public NameGenerator(FirstNameGenerator firstNameGenerator,
                         LastNameGenerator lastNameGenerator) {
        this.firstNameGenerator = firstNameGenerator;
        this.lastNameGenerator = lastNameGenerator;
    }

    public String generateName() {
        return firstNameGenerator.generate() + " " + lastNameGenerator.generate();
    }
}

However, there's an awful lot of duplication here -- in the fields and constructor, we mention a first name generator no less than six times. There's also another form of duplication present -- this constructor is identical to every other constructor I write in Java in that all it does is assign arguments to fields. After all, doing work in the constructor is a bad idea. In an ideal world, we might decide a more succinct syntax for our class would be:

public class NameGenerator(FirstNameGenerator firstNameGenerator,
                           LastNameGenerator lastNameGenerator) {
    public String generateName() {
        return firstNameGenerator.generate() + " " + lastNameGenerator.generate();
    }
}

Now, the constructor arguments are parameters for the class itself. This is functionally equivalent to the example above, while cutting down on the duplication we saw. Incidentally, Scala users will be familiar with this style of constructing objects.

The example is still in a statically typed language -- so what happens if we remove the static typing?

public class NameGenerator(firstNameGenerator, lastNameGenerator) {
    public generateName() {
        return firstNameGenerator.generate() + " " + lastNameGenerator.generate();
    }
}

I'm not sure this has really helped readability or clarity. Certainly, we haven't gotten the same gains that we got by changing from a Java constructor to using class parameters. However, if we inspect our dynamically typed example closely, we might notice that there's very little that distinguishes it from a couple of function definitions. This leads us to write code that looks something like this:

function nameGenerator(firstNameGenerator, lastNameGenerator) {
    return {
        generateName: function() {
            return firstNameGenerator.generate() + " " + lastNameGenerator.generate();
        }
    };
}

In a few simple transformations, we've moved from Java to Javascript. Instead of a class, we have a function that takes a couple of generators. This function returns a new object with a single field called generateName. It just so happens that this field is a function, which uses the two generators to generate a full name. So, to give an example usage of the class:

// Assume we already have first and last name generators
var generator = nameGenerator(firstNameGenerator, lastNameGenerator);
var name = generator.generateName();

Hopefully this is enough to persuade you that we don't need classes in order to capture what we mean by object orientation. Indeed, there are only really two language features that we've used here. The first is the object, which allows us to group together the various parts of some idea (although in this case, we only have a single field).

The second feature is functions. But just having functions is not enough -- there are many languages that have functions where this wouldn't be possible to write. To be more specific we need to have first class functions, so that we can assign a function to a field as easily as we would assign a number or a string to a field. It also helps to have anonymous functions so that we can create functions easily, in the same way that Javascript's object literal notation makes it straightforward to build objects. The other piece of the puzzle is closures. Although firstNameGenerator and lastNameGenerator were not defined in the function assigned to generateName, they can still access these values since Javascript supports closures. This means that the references to the generators are still valid even after the function nameGenerator has returned.

This raises the question of what features you actually need in a language, and what features you can implement by composing other features. Another example might be namespaces. Many languages have namespaces explicitly built into the language, but by using layers of objects, you can easily build namespaces in Javascript despite the lack of explicit support for namespaces in the language. For instance, if were writing real Javascript code, we should put our nameGenerator method into the proper namespace, rather than in the global namespace:

ZWOBBLE = ZWOBBLE || {};
ZWOBBLE.names = ZWOBBLE.names || {};

ZWOBBLE.names.nameGenerator = function(firstNameGenerator, lastNameGenerator) {
    ...
}

We can easily write a function to replace this first couple of lines, so we have:

ZWOBBLE.define("ZWOBBLE.names");

ZWOBBLE.names.nameGenerator = function(firstNameGenerator, lastNameGenerator) {
    ...
}

Again, I hope this enough to convince that there's no need in Javascript for explicit support for namespaces. I could go on, but my broader point is that by giving a language a few basic, but powerful, constructs, one can build on top of them to reach the same features that are explicitly supported in other languages.

Incidentally, I've recently read a couple of articles on object orientation that I found interesting. The first distinguishes between abstract data types and objects, while the second makes the case for tail calls in object orientation languages.

Topics: Software design

Thoughts? Comments? Feel free to drop me an email at hello@zwobble.org. You can also find me on Twitter as @zwobble.