I'm trying to access properties from a class like this:
public class Oak extends Tree //base class for tree classes
{
public static const CLASS_NAME:String = getQualifiedClassName(Oak);
public var branches:Array;
public function Oak()
{
branches =
[
"Leaf1",
"Leaf2",
];
}
The class trying to access the variable is shown here:
public class TreeTest extends BaseScreen //just extends Sprite, TreeTest is shown on the stage
{
public var oak:Tree;
private var inc:int;
public function TreeTest(pModel:Model)
{
model = pModel;
model.createTree(Oak.CLASS_NAME);
oak = model.getTree(Oak.CLASS_NAME);
inc = 0;
this.addMouseClickListener(0, treeHandler); //from BaseScreen
}
private function treeHandler(e:MouseEvent):void
{
inc++;
this.displayText.text = oak.branches[inc];
}
and the model is shown below:
public class Model
{
public var treeArray:Array;
public function Model()
{
treeArray = new Array();
}
public function createTree(pClassName:String):void
{
var name:String = pClassName;
var ClassReference:Class = getDefinitionByName(name) as Class;
var classInstance:Tree = new ClassReference;
treeArray.push([name, classInstance]);
}
public function getTree(pClassName:String):Tree
{
var treeName:String = pCharacterClassName;
var match:Boolean = false;
var matchArrayRef:int = 0;
for (var i:int = 0; i < treeArray.length; i++)
{
if (match == true)
{
break
}
if (treeArray[i][0] == treeName)
{
match = true;
matchArrayRef = i;
}
else
{
match = false;
}
}
if (match == false)
{
return null;
}
else
{
return treeArray[matchArrayRef][1];
}
}
When I ran this, I got the error "Access of possibly undefined property branches through a reference with static type Tree".
After searching for a solution, I discovered that basically means that the branches array was not in the Tree class. To confirm this, if I went and took public var branches:Array away from the Oak class and put it in the Tree class, the code worked.
Why can I not access variables defined in the Oak class, and have to put everything in the Tree base class? Even if I change var classInstance:Tree to var classInstance:Oak, or even var classInstance:Object, I still get the same error if the array is in the Oak class, not the Tree class.
Like, if I create a new class Pine which would also extend Tree, do I have to put all of Pine's variables in the Tree class too?
You are referencing the oak variable as class Tree, so any property you reference from it should be of class Tree, not any subclasses, because exactly the superclass does not have them, thus compiler does not know how to address memory from branches property. Worse, this addressing might technically differ from subclass to subclass, an that's why directly addressing properties of a subclass via reference of type superclass is not possible.
There is a workaround. You can use hasOwnProperty method defined in Object class to check whether a certain tree has branches, and then address via string-based addressing like tree["branches"].
if (tree.hasOwnProperty("branches")) {
var br=tree["branches"];
trace(br); //or whatever
}
In fact, you can even go like this:
if (tree is Oak) {
var oak:Oak=tree as Oak;
// now all oaks have branches, go get em
trace(oak.branches);
}
Still, this breaks one of the object-oriented programming's core principles - you attempt to explicitly depend on subclasses having variables of certain names and are expecting them to contain values of same meaning across subclasses. Or, with the second approach, you are trying to make code know about every single subclass of Tree that will ever exist. Effectively you try to make subclasses behave as a superclass, without giving the superclass enough properties to provide that behavior in a common manner. This is not how OOP works. So, you should first devise a superclass by assigning it properties and methods of a generic Tree. All trees have branches, so branches should belong to Tree class. All trees can grow() for example, with whatever set of parameters supplied to growth, be it humidity, warmth, soil thickness, etc, but they are the same for all subclasses of Tree, be it oaks or sequoias, thus, grow() method should belong to Tree class as well. They, however, grow differently, and respond to parameters differently, but this is where override comes to help provide different behavior in subclasses.
Related
I want to know whether in ActionScript 3 there's a way to share a same function (method) between the instances of a class definition, only referencing the same function everytime... i.e., this example should log true, but logged false (note: I'd want this to reduce duplicating functions).
class A {
function f() {}
}
trace(
(new A).f === (new A).f
)
An ActionScript 3 language specification appears to say that a prototype attribute exists, but not implemented. I've understood that individual classes have a prototype object. I've specially found a prototype property (probably inherited from Class/Object) and wonder if classes use meta functions in order to be constructed (since their type is "object"... when I test with typeof: trace(typeof A, typeof Class, typeof Object)).
My last try:
class A {}
A.prototype.f = function() {}
trace(
(new A).f === (new A).f
)
It says that f doesn't exist. I could define this class as a function instead (in order to move methods to the prototype object):
function A() {}
A.prototype.f = function() {}
, but in this way I can't control access of instance members.
AS3 uses Bound methods to ensure that inside your functions, this always points to original instance object by default.
Imagine that you pass a function f of an instance a to some other object b. When then object b calls function f, the this name still points to a inside scope of the function - this reference has to be store somewhere.
Well, you could assign function to static property of your class:
package adnss.projects.evTest
{
public class A {
private var x:Number = 5;
private var _y:Number = 0;
public function A(){}
static public const f = function (p:Number):Number {
this._y = p;
return this.x*p;
}
public function get y():Number { return _y; }
}
}
And then have access to private properties of the instance of A in that static function by explicitly using this:
var instance:A = new A();
trace("return:", A.f.call(instance, 3)); //return: 15
trace("instace:", instance.y); //instance: 3
But that is kind of tricky for possible benefits (if any).
And about prototypes. You basically don't use them in AS3. Is't there like a souvenir :) - read this for more info
I have a class which contains the following method to place an object on the stage.
public function createBox()
{
var box:Ball1 = new Ball1();
addChild(box);
box.addEventListener(Event.ENTER_FRAME, boxF);
}
What I would like to do is to is pass an object name to the method, and load that object instead, thus allowing me to use the same method for different objects.
A non-working example:
public function createBox( obj )
{
var box:obj = new obj();
addChild(box);
box.addEventListener(Event.ENTER_FRAME, boxF);
}
Is this possible?
Thanks
A more agile version of the existing answer is to use getDefinitionByName(), which allows you to construct a class based on an input string.
Using this function, you can rewrite the method to something like this:
public function produce(className:String):*
{
var type:Class = getDefinitionByName(className) as Class;
return new type();
}
Advanced:
To make this stricter and more maintainable (make it so only certain factories can create certain classes), you can use an interface to build a relationship between a given factory and the classes it can produce. A small example of this follows below.
Say a factory, EnemyFactory, creates objects that you would consider to be enemies in a game. We don't want to be able to create things like pickups, particles and other non-enemy type objects. We can create an interface IEnemyProduct which is implemented by classes that the EnemyFactory is allowed to create. The interface could be as simple as:
public interface IEnemyProduct{}
Which would be implemented by any enemy classes. The EnemyFactory's produce() function can then be modified to a more readable version, like this:
public function produce(enemyClassName:String):IEnemyProduct
{
var type:Class = getDefinitionByName(enemyClassName) as Class;
return new type() as IEnemyProduct;
}
In this case, produce() will return null if the produced class does not implement IEnemyProduct. The goal here is to make it obvious which factories are responsible for which objects, which is a big advantage once the project becomes larger. The use of an interface rather than a base class also means you can implement multiple interfaces and have the class produced by multiple factories.
You could maybe use a simple factory, or something similar:
public class Factory
{
public static function produce(obj:String)
{
if (obj == "obj1")
{
return new obj1();
} else if (obj == "obj2") {
return new obj2();
}
}
}
I have been working on an AS3 project for some while and I think that I've hit a wall. My project requires a series of elements to be arranged in a Circular List, so I copied myself in a Circular List I had done before in C#.
Problem is, that one was heavily dependant on the usage of Generics. Now I don't have those.
Here are the codes. The T variable type represents the generics that I wish existed.
Node.as:
package
{
public class Node
{
var nodeContent:T;
var nextNode:Node;
function Node(nodeElement:T)
{
this.nodeContent = nodeElement;
}
}
}
CircularList.as:
package
{
public class CircularList
{
var head:Node;
var tail:Node;
var listLength:int;
function CircularList()
{
this.head = null;
this.tail = null;
this.listLength = 0;
}
function Add(addition:T)
{
adding:Node = new Node(addition);
if(this.head == null)
{
this.head = adding;
this.tail = adding;
head.nextNode = tail;
tail.nextNode = head;
}
else
{
tail.nextNode = adding;
tail = adding;
tail.nextNode = head;
}
listLength++;
}
function Find(requested:T):Node
{
var finder:Node = null;
var searching = head;
var i:int;
while(i <= listLength)
{
if(searching.nodeContent == requested)
{
finder = searching;
}
searching = searchig.nextNode;
i++;
}
return finder;
}
}
}
Is there a way to make this thing work without the generics?
EDIT: The real problem with this is that I want the NodeContent in the Node class to be an object. I want to make a list of people sitting on a circular table, basically, but I would like to have a code that I can reuse, rather than something specifically made for this problem
From the comments it seems like your best option here would be to use an interface.
Instead of using a type have all classes T implement an interface like INode. In this interface you can define all the functionality that your type T requires and implement it as needed in each of your implementing classes. This way you can change your function signatures to take type INode instead of Class or * and have a common set of methods that these functions can act upon.
function Add(addition:INode){
//add logic on INode
}
function Find(requested:INode):Node{
//find logic on INode
}
edit: a bit of info about interfaces,
http://active.tutsplus.com/tutorials/actionscript/as3-101-oop-introduction-to-interfaces/
say we have two Classes, A, B and each of these classes have a similar method, doTrace, that needs to be implemented differently. We can define an interface, implement it in both of these classes and pass that type into any method looking to call doTrace
Start with the interface called ITraceable,
public interface ITraceable{
function doTrace():void //all methods defined in interfaces are seen as public
}
Now our two Classes, A and B
public class A implements ITraceable { //implementing our interface, when we do this we need to define all methods in ITraceable
public function doTrace():void{
trace("I am A");
}
}
Do a similar thing for B
public class B implements ITraceable {
public function doTrace():void{
trace("I am B");
}
}
Now in some outside class we want to use this
public function letsTrace():void{
doTheTrace(new A()) //I am A
doTheTrace(new B()) //I am B
}
public function doTheTrace(object:ITraceable):void { //now we can pass both A and B into this function
object.doTrace(); //since ITraceable requires all objects that implement it have this method we can guarantee it will be here
}
Hope this helps you through your application
A class has a method to update its instance properties from a given Object.
For some reason looping through properties of this fails in ActionScript 3.
I tried something like:
class myThing() {
public var A:String;
public var B:String;
public var C:String;
...
public function bindToObject( obj:Object){
for( var s in this){
if( obj.hasOwnProperty(s)) this[s] = obj[s];
}
}
}
This way the loop never executes, as if this had no properties at all.
Please advise on a smart way of copying Object properties to an instance of my class.
You might want to try doing it the other way around. Meaning, parsing the source object's properties, and assigning its values to the target object. I looked into this matter myself and here is how I implemented that method:
private function parseData(data:Object):void
{
for (var property:String in data)
if (this.hasOwnProperty(property))
this[property] = data[property];
}
It's very similar in intent to your bindToObject method, except the fact that the method parses the parameter object. The reason for choosing this way, was that in my context, all the properties of the target object, were considered optional (i.e. if I pass only one property for the source object, out of 3, for instance, then it wouldn't make sense to parse all of the target object's properties). The context might be different in your case, however.
class myThing() {
public var A:String;
public var B:String;
public var C:String;
...
setPropertyIsEnumerable("A");
setPropertyIsEnumerable("B");
setPropertyIsEnumerable("C");
Assuming your class extendts Object.
I am working on a text adventure game which will have at least a few components (a text area for narrative and text input for user input) on the stage at all times. Therefore, I have created those components statically through Flash's WYSIWYG design environment. I gave them instance names "myTA" and "myTI" respectively. I was able to get my main class (the document class for the stage) to interact with them (dynamically adding text one character at a time like a typewriter at runtime), but other classes in the same package don't seem able to recognize the stage components. Below is the relevant code:
Case A, in which everything happens within the Main class:
package {
public class Main extends MovieClip {
public var myTA:TextArea;
var displayedChar:String = new String();
var textToWrite:String = new String();
var i:int = 0; var intervalId:uint;
var done:int = 0;
public function Main {
setUpTA();
}
public function setUpTA(){
myTA.text = "" + playAtInterval("Hello Player!");
}
public function writeCharsSlowly(){
textToWrite = arguments[0];
displayedChar=textToWrite.substring(i,i+1);
myTA.appendText(displayedChar);
i++;
if (i == textToWrite.length) {
done = 1;
clearInterval(intervalId);
}
}
public function playAtInterval(theText:String) {
i = 0;
intervalId = setInterval(writeCharsSlowly, 100, theText);
}
}
}
Case B, where Main calls on a second class 'TypeWriter' to handle the typewriter-printing:
Main:
package {
public class Main extends MovieClip {
public var myTA:TextArea;
public var myTI:TextInput;
var str:String = new String();
public function Main{
testTypeWriter();
}
public function testTypeWriter(){
typeW.playAtInterval("Hello Player");
typeW.addEventListener(MouseEvent.CLICK,testTypeWriter2);
typeW.addEventListener(KeyboardEvent.KEY_DOWN,inputEngine2)
addChild(typeW);
}
public function testTypeWriter2(event:MouseEvent){
if (myTI.text == "a") {
typeW.playAtInterval("yo");
} else {
typeW.playAtInterval("Greetings, I am a test...");
}
addChild(typeW);
}
public function inputEngine2(event:KeyboardEvent){
str = String.fromCharCode(event.charCode);
myTI.appendText(str);
}
TypeWriter:
package {
public class TypeWriter extends MovieClip {
public var myTI:TextInput;
public var myTA:TextArea;
var i:int = 0;
var done:int = 0;
var intervalId:uint;
var displayedChar:String = new String();
var textToWrite:String = new String();
public function TypeWriter(){
///nothing here
}
public function writeCharsSlowly(){
textToWrite = arguments[0];
displayedChar = textToWrite.substring(i,i+1);
myTA.appendText(displayedChar);
i++;
if (i == textToWrite.length) {
done = 1;
clearInterval(intervalId);
}
}
public function playAtInterval(theText:String) {
i = 0;
intervalId = setInterval(writeCharsSlowly, 100, theText);
}
}
}
Case A works, but in case B Flash is giving me the error "Error #1009: Cannot access a property or method of a null object reference" and notes the first line in TypeWriter where I try to operate on myTA as the problem.
how can I make other classes besides the document class 'aware' of existing stage components?
Thanks,
CCJ
I would recommend the Service Locator Pattern for this. The most naive approach would be to create a resource class which contains public static variables. Then in your document class you assign the stage instances to the corresponding static variable in the resource class. Then you can simply access these stage components anywhere.
var someTextArea = Resource.TA; //probably should rename to something more meaningful
For something a little more ingenious you should read the article I linked to.
I think this is better than the dependency injection as constructor injection could lead to huge parameter list as you might add more items to the stage, and I am not so fond on setter injection as it is easy to forget to set them.
EDIT:
Just to make it a bit more clear I thought I would add some code :)
Resource class
package
{
//TODO imports
public class Resource
{
public static var TA:TextArea;
public static var TI:TextInput;
}
}
Document class
//....setup function
Resource.TA = myTA; //myTA is the name of the instance on stage
Resource.TI = myTI;
Foo class
Resource.TA.x = 100;
//or
_myClassMemberVariable = Resource.TA;
_myClassMemberVariable.x = 100;
I think some dependency injection will solve this problem. When you instantiate your Typewriter class, pass references to myTA and myTI. ex:
public function Main{
testTypeWriter(this.myTA, this.myTI);
}
Then your Typewriter constructor should look like this:
public function TypeWriter(ta:TextArea, ti:TextArea){
this.myTA = ta;
this.myTI = ti;
}
This also has the benefit of making your application less tightly coupled, so for example you can reuse your Typewriter class with a different text area and text input.
Edit
Some extra info that may help you in the future: you can access stage elements through the root object. But this only works with objects that have been added to the display list. Let's say that Typewriter represents an object in the display list, you could then access myTA like this:
MovieClip(root).myTA
(Change MovieClip to Sprite if that's what your document class extends).
However, since it seems that Typewriter does not get added to the display list, I recommend using my first suggestion of dependancy injection.
Also check out this page, it dicusses using CasaLib to access the stage from any object. I personally haven't tried it, so that's why it's at the end here ;-)
Are myTA and myTI actually on the stage at author time or are the added dynamically?
In the first case, just add an instance name to each. Then add a variable to each class
var main_mc : Main = root as Main;
You can then access the instances via main_mc.myTA and main_mc.myTI (assuming those are the instances names you chose) and everything should be type-safe.
In the dynamic case, just make sure you save off references in the main class to each as you add them.
Another option is to have classes for myTA and myTI send an event in their constructor to announce their existence. The main class can then listen for these and register the references.
To be honest, though, you are mixing up your display with your logic. Read up on MVC and PureMVC in particular. With a good design, everything can be handled by messages, and the instances don't need direct references to each other. IIRC, Moock's AS3 book has a chapter on MVC (but it could be his AS2 book).
The fact that they will be on stage at all times shouldn't stop you from creating specific classes for them.
Depending on your game structure, you could either create a MovieClip with your TextFields and link them to the Typewriter class, or simply create a class for the TextFields and use Events to modify the text content.
You're using external classes so there are no reason to keep any kind of logic inside Flash CS.
Just to be clear. When you are writing stage you really mean the document class for your flash document. Stage is a class that every instance that has been added to the displaylist Have access to.
I would pass the textfields into the classes that needs to update them.
var tw : Typewriter = new Typewriter();
tw.inputField = myTI;
tw.textArea = myTA;
Or
var tw : Typewriter = new Typewriter(myTI, myTA);