Friday, November 13, 2009

rethinking constructors -- or what I learned from objective-c

Like many Flash developers, I've been studying Objective-C in a desperate attempt to ride the iPhone app train. (Though I think it left the station while I was still putting a token in the turnstile.)

One thing I like about Objective-C is its version of constructors, which usually start with the word "init." For instance, a method might be called just init(), or, if it takes arguments, it might be called initWithColor( 0xFF000 ) or initWithColorAndType( 0xFF0000, "large" );

Note: objective-C's syntax is different from Actionscript's, so in reality, the last method call above would look like this: [myObject initWithColor: 0xFF0000 andType: @"large"]; Since this post is about Actionscript, I'll keep everything in AS syntax.

You can, in a sense, overload Objective-C constructors. A class can't have two init() methods. But it can have init(), initWithColor() and initWithColorAndType(). You call the appropriate one, depending on how you want to initialize the object.

I also like the fact that every Objective-C class has a formal interface (defined in its header file). I do that for my Actionscript classes, too.

All of my classes (except DTOs and utilities with static members) implement at least one interface. Often, several classes will share the same interface. But even if a class is a loner, I make an interface file for it. I like this this clean separation between interface and implementation. When you browse my code, you can learn how everything fits together just by reading the interface files.

But you can't define constructors in an interface! That bothers me, because a constructor is -- or can be -- a way of communicating with a class. And if something is part of the way one communicates with a class, it is, by definition, part of that class's interface.

(I can't use my TV until I first turn it on. So the power button is kind of like a constructor. But it would be insane to say that the power button is not part of the TV's interface.)

Traditionally, interface files are only used when they are meant to be shared by multiple classes. In that sense, I get why they can't include constructors. Class A and Class B can't share a constructor, because Class A's constructor must be named A() and Class B's must be named B().

But since I make separate interface files for each class, I'd like to list ALL class inputs and outputs in those interface files.

Here's my solution: I never pass ANYTHING into a constructor. In other words, I would never write a constructor like this:

public function Person( name : String ) { ... }

Instead, person's constructor will look like this:

public function Person() { ... }

I don't care that the Person() method can't be listed in an interface file. It's not used as an interface element. It doesn't suck anything in from the outside world.

That's not to say I won't write a constructor function. I will if the class has any internal housekeeping it needs to do at start-up:

public function Person()
{
_kids = new Array();
}

So how do I specify that this a Person is named "George"?

var dad : Person = new Person();
dad.initWithName( "George" );

If it's also legal to have a nameless person, I would write this:

var dad : Person = new Person();
person.init();

All my classes have at least an init() method, even if it is just a placeholder husk. Want to know if my classes can be initialized any other way? Just check their interfaces:

public class Person implements ILifeform...

public interface ILifeform
{
function init() : void;
function initWithName( name : String ) : void;
function initWithNameAndGender( name : String, isMale : Boolean );
function eat() : void;
function die() : void;
function die() : void;
function addKid( name : String ) : void;
}

One objection to my system might be that initializer-method names could get absurdly long:

initWithWarpDriveShieldsPhotonTorpedosHoloDecksAndTransporters( ... );

To me, that's not an objection -- it's a warning! Even without my system, I would never write code like this:

var myShip : SpaceShip = new SpaceShip( true, false, true, true, false );

The more arguments a method takes in, the harder the code is for me to read, and the more apt I am to make a mistake. I have a loose rule of thumb that methods should take in no more than three arguments (and three is really pushing it).

So I'd change the above long init method to this:

initWithSpecs( specs : SpaceShipSpecs );

And I'd back this up with DTO class called ShipSpecs that had properties like warpDrive, sheilds, etc.

1 comment:

  1. I like it though it adds some complexity to defining classes.

    A reason for constructors being intergrated into the initialization of the object is so that you can never have an object with an inconsistent state.

    Also for immutable objects, which, in my opinion, should be virtually all objects.

    So my complexity point means you need code to deal with the uninitialized object so that no method can be called until an init method is called. And this needs to be something done in _every_ method. That's a ton of duplication since, as far as i know, there's no easy way to delegate to a single function to check constraints prior to executing the body of every function in a class.

    ReplyDelete