I am defining this function in one of my classes:
public function onUse():void {};
Then in another of my classes (let's call it "class2"), I create a object of this class, and then want to override this function with another one. After some Google-Fu, I have found this, and used it...
button.onUse {
variable = value;
}
...but it executes instantly, and not when onUse() is called - which seems to be an empty function, always.
I didn't find anything more than that - I tried a few things myself, like specifying a function inside class2 and using button.onUse = function();, but it always throws errors.
Can anyone tell me whether what I am trying to do is actually possible, and if it is, how can I do it?
You can only override functions when you are extending the class:
public class A {
public function foo():void {
doStuff();
}
}
public class B extends A {
override public function foo():void {
doOtherStuff();
}
}
var n:A = new A();
n.foo(); // => calls doStuff();
var o:B = new B();
o.foo(); // => calls doOtherStuff();
Hence, assigning a different function to a class method of an instance is not possible at runtime.
You can, however, let your original class contain a field of type Function, and then simply assign a different closure to it.
public class A {
public var foo:Function;
}
var n:A = new A();
n.foo = function ():void {
doStuff();
};
n.foo(); // => calls doStuff();
var o:A = new A();
o.foo = function ():void {
doOtherStuff();
}
o.foo(); // => calls doOtherStuff();
check the syntax of
button.onUse {
variable = value;
}
a function would be defined as
public function onUse():void {};
and overwritten with
override public function onUse():void {
}
in a different class
the way you're trying to do it, does not constitute overriding a function.
What I've done in similar circumstances is create a onClickFunction function in the class
public var onClickFunction:Function = null;
and then in the CLICK event listener function add
if(onClickFunction != null){
onClickFunction();
}
then you can assign your on-click functionality by doing something like this
button.onClickFunction = function():void{
variable = value;
// dostuff
}
this is not the best way of doing it, but probably the easiest way of implementing the functionality. And ideally you'd use inheritance the way the spacepirate suggested.
Related
i am trying to get an Array from a class to an other class but i can't use static or global variable for it.
in my class Jeu.as, i have 3 arrays (t_map1, t_map2 and t_map3) that represents my game map. t_map is an array that can content one of those map and a place where i can change it. I want to take the map use (form t_map) to my character (Perso.as) so it can know where it can walk or not.
The problem is that i don't know how to bring t_map from Jeu.as to Perso.as... I have tried to use a static variable (as seen in other answer) but it don't work because the map have to change...
How can i create a variable that can contain my array in my perso.as class?
in short, i want to bring t_map values form my jeu.as to an other variable in perso.as
All you really need to do is give both instances a reference to the same arrays, or give Perso a reference to Jeau. Static variables are a really bad idea, even if there's nothing inherent to this situation that would keep them from working for you.
Here's what a solution that uses Dependency Injection would look like:
package model {
public class Jeau extends EventDispatcher {
protected var _tMap1:Array = new Array();
protected var _tMap2:Array = new Array();
protected var _tMap3:Array = new Array();
//consider using more descriptive variable names
//or an array of arrays (one map in each index)
public function get tMap1():Array {
return _tMap1;
}
public function set tMap1(value:Array):void {
if (value != _tMap1) {
_tMap1 = value;
dispatchEvent(new Event('tMap1Changed'));
}
}
public function get tMap2():Array {
return _tMap2;
}
public function set tMap2(value:Array):void {
if (value != _tMap2) {
_tMap2 = value;
dispatchEvent(new Event('tMap2Changed'));
}
}
public function get tMap3():Array {
return _tMap3;
}
public function set tMap3(value:Array):void {
if (value != _tMap3) {
_tMap3 = value;
dispatchEvent(new Event('tMap3Changed'));
}
}
protected function somethingThatChangesMap1(index:int, value:String):void {
_tMap1[index] = value;
dispatchEvent(new Event('tMap1Changed'));
}
}
}
I've assumed this is a View class--you haven't given many details. You listen for events coming out of the model Class and then update the View based on whatever is in those arrays. By getting the whole instance, you have the ability to listen for these events. Otherwise, you'd have to use some other mechanism to communicate the change (such as the event bus used in Roenter link description herebotLegs).
package view {
class Perso extends MovieClip {
protected var jeau:Jeau;
public function get jeau():Jeau {
return _jeau;
}
public function set jeau(value:Jeau):void {
if (value != _jeau) {
_jeau = value;
_jeau.addEventListener('map1Changed', doMap1Stuff);
_jeau.addEventListener('map2Changed', doMap2Stuff);
_jeau.addEventListener('map3Changed', doMap3Stuff);
doMap1Stuff();
doMap2Stuff();
doMap3Stuff();
}
}
protected function doMap1Stuff(e:Event=null) {
//do actions depending on the state of map1 here
}
protected function doMap2Stuff(e:Event=null) {
//do actions depending on the state of map2 here
}
protected function doMap3Stuff(e:Event=null) {
//do actions depending on the state of map3 here
}
}
}
This is just an example of how you'd use a third Class to combine the first two. I wouldn't necessarily do it exactly like this:
package control {
public class MainGame {
protected var jeau:Jeau;
protected function perso:Perso;
public function MainGame() {
jeau = new Jeau();
//jeau setup
perso = new Perso();
perso.jeau = jeau;
}
}
}
Sounds like you need some simple accessors.
In Jeu, you'll want something to retrieve the maps like this:
function getMap(mapNumber:int):Array
{
switch(mapNumber)
{
case 1:
return t_map1;
case 2:
return t_map2;
case 3:
return t_map3;
default:
trace("Error: that's not a valid map number!")
}
}
If you saved your maps in another encompassing Array (lets call it allTheMaps), the function would look much nicer:
function getMap(mapNumber:int):Array
{
allTheMaps[mapNumber];
}
Then in Perso, you need to store a reference (or several if it needs to know about multiple maps at the same time) of an array to store the map in. You'll also need a function to set the data:
var theMap:Array;
function setMap(theMap:Array):void
{
myMap = theMap;
}
Now you can pass a map from an instance of Jeu to an instance of Perso:
var Jeu = new Jeu();
var Perso = new Perso();
...
Perso.setMap(Jeu.getMap(1));
I apologize in advance if my question is not clear, because I don't know how to put this.
What I am trying to do is to reduce few lines of repeated code by implementing various OOP methods/concepts.
The problem
I have few set of of classes which has initialization process. So, I am implementing an init() method in all those classes. From the calling class (main), these objects will be instantiated and init() method of each object is called in the the order and call some other process after all of them are initialized.
Something like this
public function mainClass(){
_obj1 = new Class1();
_obj1.init();
_obj2 = new Class2();
_obj2.init();
_obj3 = new Class3();
_obj3.init();
doSomething();
}
Well, its not a big deal, but some of the classes' init() methods are asynchronous and I need to add an event listener to get notified when they have finished initialization.
I tried that by extending EventDispatcher for each of those classes and dispatch event and handle it. I even implemented a logic to handle multiple asynchhnous calls by maintaining a counter.
It will be a painful job for me whenever I need to add a new class. I thought I could untilize OOP and reduce and simplify the code.
So I came up with some thing like this, which is currently not possible (abstract class).
abstract class Initializable
{
private var _callBack:Function;
//implement initializaton process in this method
function init(callback:Function=null):void;
protected function get callback():Function{
return _callBack;
}
protected function set callback(func:Function):void{
_callBack = func;
}
protected function onComplete():void{
if (_callBack){
_callBack(this);
}
}
}
This is the main problem for me, as you know abstract class is not allowed in AS3, and the "this" refers to the Initializer class but not its subclass I guess.
This is what I am asking for your help (for the hack)
I need it very much to make my system design simple and flexible, because I can extend the solution to allow mass synchronous initialization which will allow to easily queue up all objects in the order and call init() one after the other in the order in which they are added.
The mass initializer which takes care of handling the asynchronous job
public class MassInitializer
{
private var _objList:Array; //holds objects
private var _callBacks:Array;
private var _onComplete:Function;
public function MassInitializer()
{
_objList = new Array();
}
public function add(obj:Initializable,callback:Function=null):void{
_objList.push(obj);
_callBacks.push(callback);
}
public function init():void{
for (var i:int = 0;i < _objList.length;i++){
_objList.init(this);
}
}
private function onProgress(obj:Initializable):void{
//do updates here
for (var i:int;i<_objList.length;i++){
var obj:Initializable = _objList[i];
var fun:Function = _callBacks[i];
//update progress
if (fun){
fun(obj);
}
_callBacks.splice(i,1);
_objList.splice(i, 1);
}
if (_objList.length == 0){
onComplete();
}
}
private function onComplete():void{
_onComplete(this);
}
}
the main (manager/caller) class (ClassA, ClassB are subclasses of Initialzable class)
public class MainClass
{
private var _obj1:ClassA;
private var _obj2:ClassB;
public function MainClass()
{
_obj1 = new ClassA();
_obj2 = new ClassB();
}
public function init():void{
var initManager:MassInitializer = new MassInitializer();
initManager.add(obj1);
initManager.start();
}
}
probably I am trying to (or want to )implement an observer pattern, but I don't want to confuse you by saying it in advance. Oops I said it? please ignore.
You can emulate abstract classes in ActionScript by enforcing method overrides: Just throw an error if the "abstract" method is called. I like to also implement an interface, but that's not a must, of course:
public interface Initializable
{
function init (callback : Function = null) : void;
function get callback () : Function;
function set callback ( callback : Function ) : void;
}
public class AbstractInitializableImpl implements Initializable
{
private var _callBack:Function;
protected function init(callback:Function=null):void {
throw new Error ("You must implement the init() method!");
}
protected function get callback():Function {
return _callBack;
}
protected function set callback(func:Function):void {
_callBack = func;
}
protected function onComplete():void {
if (_callBack){
_callBack(this);
}
}
}
It's not a 'hack'. It's also very simple. Make each subclass implement an interface, instead of extend an abstract class.
Here is the adobe reference on AS3 interfaces.
What I am trying to do is kind of odd, but I am wondering if anyone can come up with a clever way to do what I want to do. Basically, I want to re-define a named function at runtime. I can do this with anonymous functions, but I can't figure out a way to do it for named functions. I want to do this so that I can implement a "spy" functionality on an object for a testing framework (a port of Jasmine to Flex).
Take, for instance, this class:
public class TestClass
{
public var anonymous:Function = function():void {
trace("original anonymous");
};
public function named():void {
trace("original named");
}
}
I can easily re-define the anonymous function because it is just a variable. Javascript uses this idiom a lot.
var testClass:TestClass = new TestClass();
testClass.anonymous = function():void { trace("overridden anonymous"); }
BUT, when I do the same thing for named functions, you get a compile-time error:
// Does not compile
testClass.named = function():void { trace("overridden named"); }
I tried to make it a bit more "squishy" but this leads to a runtime failure "Cannot assign to a method named on TestClass".
// Compiles with runtime failure
testClass["named"] = function():void { trace("overridden named"); }
Can anyone more clever than I come up with a way to hack this? Can the bytecode be hijacked? Something?
I want to modify an object, not a
class
But object doesn't contain functions, only non-static variables. I tried to use prototype property and replace method there, but original method still gets called instead of injected one.
About "hack" bytecode, do you mean "hack" already loaded SWF in runtime? I think it's not possible. I'm sure, though, you can parse SWF with something like as3swf, find method in bytecode, replace it and save result in new SWF.
I had an idea bout making a function "cache" . This might work with what you need.
Let's say you have a class "Car" with a method you need to redefine at runtime:
public class Car extends Sprite
{
private var functionCache:Function;
public function Car()
{
super();
}
public function flexibleFunction(functionBody:*=null):void{
if(functionBody is Function){
functionBody.call();
functionCache=functionBody;
} else {
functionCache(functionBody);
}
}
}
Usage:
public class Main extends Sprite
{
private var car:Car;
public function Main()
{
car = new Car();
car.flexibleFunction(function(){trace("redefine test #1")});
car.flexibleFunction();
car.flexibleFunction(function(doParametersWork:String="let's see"){trace("redefine test #2: " + doParametersWork);});
car.flexibleFunction("yes they do");
car.flexibleFunction();
}
}
an easy way to accomplish what you want is to simply pass a new function to the original function and execute it from there:
package
{
//Imports
import flash.display.Sprite;
//Class
public class RedefineFunction extends Sprite
{
//Constructor
public function RedefineFunction()
{
originalFunction();
originalFunction(redefinedFunction);
}
//Original Function
public function originalFunction(redefinition:Function = null):void
{
if (redefinition != null)
redefinition();
else
trace("Original Function Definition");
}
//Redefined Function
private function redefinedFunction():void
{
trace("Redefined Function Definition")
}
}
}
traces:
Original Function Definition
Redefined Function Definition
This is a hard question to do, but I'll try to explain.
I have the Class and the parameters of its contructor as an object. What I need to do is a function that returns an instance of this class, passing this parameters to the constructor.
This is the code:
Some random and unmodifiable class:
public Foo {
public function Foo(a:int, b:String) {
// constructor
}
}
And some function (in some another class):
function bar(params:Object):* {
var baz:Foo = new Foo(params.a, params.b);
return baz;
}
What I need to do is make this function generic, without pass params as parameter to Foo constructor because I can't modify it. Something like:
function bar2(clazz:Class, params:Object):* {
var baz:* = new clazz(/*some magic way to transform params in comma separated parameters*/);
return baz;
}
Anyone can help me?
Thanks a lot.
This is called parameterized factory. First I thought about Function.apply, but it doesn't apply to constructors (he-he). So, people are making factories like this:
function create(what:Class, args:Array):* {
switch (args.length) {
case 0: return new what();
case 1: return new what(args[0]);
case 2: return new what(args[0], args[1]);
...
//PROFIT!
}
throw new Error("Need moar cases!");
}
what about using ByteArrayto copy the object ?
function clone(source:Object):* {
var copier:ByteArray = new ByteArray();
copier.writeObject(source);
copier.position = 0;
return(copier.readObject());
}
newObjectCopy = clone(originalObject);
source
If you have the option of not using a constructor, but adding an initialise() function to each class which can be constructed instead, you could use Function.apply - something like in the example below.
public class ThingCreator
{
public static function createTheThing(c:Class, params:Array):Object
{
var the_thing:Object = new c();
the_thing.initialise.apply(the_thing, params);
return the_thing;
}
}
As alxx pointed out above, Function.apply and AS3 reflection in this case does not seem to work with AS3's constructors.
If an AS3 method returns a reference to a complex type, is there any way to make that 'readonly', like how you can have const member functions in C++? An architecture I want to use calls for a class building itself from a passed template object... and really the template object should not be modifiable. I'm currently forced to add call-back enumerators and/or lots of extra accessor methods.
Flex has an ObjectUtil.clone() method that will make a deep copy. The copy will still by modifiable, but since it's a copy, the changes won't propagate back to the original.
The method is no complicated so if you're not using Flex, just add this to a util class:
public static function copy(value:Object):Object
{
var buffer:ByteArray = new ByteArray();
buffer.writeObject(value);
buffer.position = 0;
var result:Object = buffer.readObject();
return result;
}
There is no way to do that in AS3, there is Sam's way of doing it, but it still requires copying that object before you return it, depending on the complexity of that object, it can impact the performance.
Immutable interfaces are a near-equivillant to const-correctness. Here's an example:
interface CPoint {
function get x():Number;
function get y():Number;
}
class Point implements CPoint {
private var _x:Number;
private var _y:Number;
public function get x():Number { return _x; }
public function get y():Number { return _y; }
public function set x(val:Number) { _x = val; }
public function set y(val:Number) { _y = val; }
public function normalize():void {
var length:Number = Math.sqrt(_x*_x + _y*_y);
_x /= length;
_y /= length;
}
public function Point(x:Number, y:Number) {
_x = x; _y = y;
}
}
If you return a Point as a CPoint reference, then its fields cannot be altered. You can do an explicit cast to a Point from a CPoint to force access, but you can do the same thing with const casting in C++.
Unfortunately, AS3 doesn't support covariance like it should, so things get unnecessarily difficult for const sub-objects. For example, if you had a Line class that was made up of two points, you might want to say line.start.x = 47; if you have full access to the line, but allow reading of line.start.x through an immutable interface. You could do this if there was covariance, but instead you'll need to add separate get properties for mutable and immutable properties. So, you'd end up instead with line.cstart.x for reads from a CLine. Something like this:
interface CLine {
function get cstart():CPoint;
function get cend():CPoint;
}
class Line implements CLine {
private var _end:Point;
private var _start:Point;
public function get cend():CPoint { return _end; }
public function get cstart():CPoint { return _start; }
public function get end():Point { return _end; }
public function get start():Point { return _start; }
public function Line(x1:Number, y1:Number, x2:Number, y2:Number) {
_start = new Point(x1, y1);
_end = new Point(x2, y2);
}
}
I would create a flash.utils.proxy object. You could create a proxy object that has read only implementation of a child that is passed in.
Here is the documentation for creating a proxy object. http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/utils/Proxy.html
Note: Proxy is pretty damn slow, since you'll be bypassing native object checking, and replacing it with a function call -- which when using a lot will be slow. I would do some simple performance testing first.
note: This is pseudo-code.
use namespace flash_proxy;
dynamic class ReadOnly extends flash.utils.Proxy {
private var target:Object;
public function ReadOnly(target:Object) {
this.target = target;
}
flash_proxy function getProperty(name:*):*
return target[name];
}
flash_proxy function setProperty(name:*, value:*):void
// throw an error or do nothing
}
}
You could then do:
var readOnly:ReadOnly = new ReadOnly(stage.loaderInfo.parameters);
readOnly.someparameter = 'newvalue';
trace(readOnly.someparameter); // should be old value