Showing posts with label actionscript. Show all posts
Showing posts with label actionscript. Show all posts

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.

three little tricks

Here are a couple of tricks I learned form this article:

I try never to access an array's length in a for loop:

for ( var i : int = 0; i < people.length; i++ ) ...

That's bad, because each time the loop iterates, it has to dig into people and access its length property. I get a boost in optimization (without losing clarity) by doing this:

var numPeople : int = people.length;
for ( var i : int = 0; i < numPeople; i++ ) ...

But I'm bothered by the fact that both i and numPeople are throw-away variables (just needed for loop mechanics), yet only one is declared outside the for statement. So here's the trick:

for ( var i : int = 0, numPeople = people.length; i < numPeople; i++ ) ...

I advocate either that or this:

var i : int = 0;
var numPeople : int = people.length;
for ( i; i < numPeople; i++ ) ...

Note: you could also write the for loop this way:

for ( ; i < numPeople ; i++ )...

Trick two:

Let's say you need to loop backwards through an array by index. You could do this:

var arr : Array = [ 10, 20, 30, 40, 50 ];
for ( var i : int = arr.length - 1; i <= 0; i-- )
{
trace( arr[ i ] );
}

//output: 50, 40, 30, 20, 10

or (here's the trick) you could just do this:

var arr : Array = [ 10, 20, 30, 40, 50 ];
var i : int = arr.length - 1;
while( i-- )
{
trace( arr[ i ] );
}

//output: 50, 40, 30, 20, 10

Why does that work? Won't i keep decrementing past zero (-1, -2, -3...)? No, because in the Boolean sense, zero reads as false (all other numbers read as true). So while( 0 ) will stop the loop.

One final trick:

I didn't learn this one from the article I liked to above. I've seen it in various places.

Let's say you want to either set a variable equal to a value read in from an xml file OR, if there is no value in the xml file, you want to set the variable to some default value. (Hint: note the word "or" in the previous sentence.)

Here's what I used to do:

if ( valueFromFile == null ) speed = valueFromFile;
else speed = 10;

or I might have done this:

speed = ( valueFromFile == null ) ? valueFromFile : 10;

or even this:

speed = ( valueFromFile ) ? valueFromFile : 10;

What I hate about all those versions is the stuttering of "valueFromFile." Via the trick, I can eliminate it like so:

speed = valueFromFile || 10;

I love how clean this looks, especially when you're using it with multiple values:

var speed : int = speedFromFile || 10;
var color : uint = colorFromFike || 0xFF00FF;
var name : String = nameFromFile || "George";

getters and setters: no more faux properties

I am obsessed with making my code as easy to use as possible. One way I do that is to stay consistent whenever I can. If class A's kill() method does the same thing as class B's destroy() method, they shouldn't have two different names. They should both be kill() or destroy().

Adobe is terribly inconsistent (probably due to too many developers working without tight supervision) when deciding if properties should be accessed via methods or faux properties (getters/setters). Is it length or length(). Depends: arrays have length; XMLLists have length(). That always trips me up.

Accessors can present themselves as either properties or methods. For consistency, I've decided to use only one of these. It doesn't make sense to forgo methods, since I have to use methods for other purposes. So I'm forgoing properties.

No more "public function get length()" for me. From now on, I'll use getLength() and setLength(). For Booleans, I'll use isCold() and hasChildren(). Anyone using my classes -- including me -- will no longer have to look up a member to see if it's accessed as a property or method. It will always be a method.

Of course, another clean way of working is to be very rigid about whether a member is something a class has or something a class can do. A class might HAVE length but it can't DO length. I like this distinction in theory, but I'd rather not have to think about it in practice -- especially since some cases are ambiguous: does a book flipTo() page 18 or is its currentPage page 18?