Joshua Zollinger
Joshua Zollinger

Reputation: 757

How to choose which child class to instantiate dynamically

My current project is in as3, but this is something I am curious about for other languages as well.

I'm attempting to use a factory object to create the appropriate object dynamically. My LevelFactory has a static method that returns a new instance of the level number provided to the method. In the code calling that method, I am able to dynamically create the buttons to call the levels like so:

for (var i:int = 1; i < 4; i++) {
            var tempbutton:Sprite = createButton("Level " + i, 25, 25 +(60 * i), start(i));
            _buttons.push(button);
}

This code just creates a simple button with the given arguments (ButtonText, x, y, function). It's working fine. The buttons are created, and clicking on one of them calls this method with the appropriate argument

private function start(level:int):Function {
        return function(e:MouseEvent):void {
            disableButtons();
            newLevel = LevelFactory.createLevel(level);
            addChild(newLevel);
        }
}

This is all working fine; I'm just providing it for background context. The question I have is this: Is it possible to dynamically choose the type of object that my static function returns? Currently, I have am doing it as follows

public static function createLevel(level:int):Level {
        var result:Level;
        switch(level) {
            case 1: result =  new Level1(); break;
            case 2: result = new Level2(); break;
            //etc
        }
        return result;
}

I should note that all of these Level1, Level2, etc. classes extend my base level class. (Yay polymorphism!) What I would like to do is be able to do something along the lines of

public static function createLevel(level:int):Level {
        var result:Level;
        var levelType:String = "Level" + level;
        return new levelType();
}

Obviously it's not going to work with a string like that, but is there any way to accomplish this in as3? What about other languages, such as Java or Python? Can you dynamically choose what type of child class to instantiate?

Update:

import Levels.*;
import flash.events.*;
import flash.utils.*;

public class LevelFactory
{

    public static function createLevel(level:int):Level {
        var ref:Class = getDefinitionByName('Levels.' + 'Level' + level) as Class;
        var result:Level = new ref();
        return result;  
    }
}

Update/Edit: getDefinitionByName seems to be what I'm looking for, but it has a problem. It seems that the compiler will strip unused imports, which means that unless I declare each subclass in the code ahead of time, this method will get a reference error. How can I get around the need to declare each class separately (which defeats the purpose of dynamic instantiation)?

Upvotes: 0

Views: 411

Answers (2)

Joshua Zollinger
Joshua Zollinger

Reputation: 757

Since Andrey didn't quite finish helping me out, I am writing up a more complete answer to the question after much research.

getDefinitionByName definitely has the use I am looking for. However, unlike its use in Java, you HAVE to have a hard reference to the class you want instantiated somewhere in your code. Merely imported the class is not enough; the reason for this is that the compiler will strip the reference from any unused import to save space. So if you import the package of classes you want to choose dynamically but don't have a hard reference to them, the compiler will de-reference them. This will lead to a run-time error when the program cannot find the appropriate reference to your class.

Note that you don't actually have to do anything with the reference. You just have to declare a reference so that it can be found at run-time. So the following code will work to eliminate the switch-case statement and allow me to dynamically declare which class I am using at run-time.

{
import Levels.*;
import flash.events.*;
import flash.utils.*;
/**
 * 
 * Returns the requested level using the createLevel class
 * ...
 * @author Joshua Zollinger
 */
public class LevelFactory
{
    Level1, Level2, Level3, Level4, Level5, Level6, Level7;

    public static function createLevel(level:int):Level {
        var ref:Class = getDefinitionByName('Levels.Level' + level) as Class;
        var result:Level = new ref(); // it will actually be the correct class
        return result;
    }}}

The obvious downside to this is that you still have to have a hard-coded reference to every class that can be instantiated like this. In this case, if I try to create a Level8 instance, it will through a run-time error because Level8 is not referenced. So every time I create a new level, I still have to go add a reference to it; I can't just use the reference dynamically.

There are supposedly ways around this that I have not tested yet, such as putting the code for the classes in a separate SWF and importing the SWF at run-time or using outside libraries that will have different functionality. If anyone has a solid way to get a truly dynamic reference that doesn't require a hard coded reference anywhere, I would love to hear about it.

Of course, it's still a lot cleaner this way; I don't have a extensive switch case statement to pack all the levels. And it's easier and faster to add a reference to the list than creating a new case in a switch. Plus it is closer to dynamic programming, which is usually a good thing.

Upvotes: 0

Andrey Popov
Andrey Popov

Reputation: 7510

Yes, you sure can, and it's very similar to the string thing that you've provided. The only thing that you are missing is the getDefinitionByName method: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/utils/package.html#getDefinitionByName()

You can generate whatever class name you want, and what this method does is that it searches for that class in it's namespace, and if it finds it - it returns it as a class:

var ClassReference:Class = getDefinitionByName("flash.display.Sprite") as Class;
var instance:Object = new ClassReference();

This piece of code will instantiate a Sprite. This way you can instantiate your classes without all those switches and cases, especially when you have to make a hundred levels :)

Hope that helps! Cheers!

Edit:

In your case, the code should be:

var ref:Class = getDefinitionByName('com.path.Level' + index) as Class;
var level:Level = new ref(); // it will actually be Level1 Class

Upvotes: 1

Related Questions