3

I have an OOP related problem with Flash, actionscript 3. It's a personal project, and I am looking for a design pattern or workaround for this problem, and my goal is to learn new things.

I have created a class called Chain. I created this util-class to make delayed function calling easy. You can make a chain of functions, by adding them with a delay in milliseconds. This chain can be executed multiple times, even in reversed order. This class has functions which returns itself. That makes it possible to have a jQuery styled syntax like this:

var chain:Chain = new Chain(); 
chain.wait(100).add(myFunction1,300).wait(300).add(myFunction2,100);
// etc..

For the example I have left lots of functions just to demonstrate the problem. The Chain class is mostly pure for adding functions and start/stopping the chain.

public class Chain 
{  
 function wait(delay:int = 0):Chain
 {
   // do stuff
   return this;
 }

 public function add(func:Function, delay:Number = 0):Chain
 {
      list.push( new ChainItem(func, delay) );
      return this;
 }
}

Now, I have a another class called ChainTween. I am trying to split things up to keep the Chain with some core functions and have ChainTween do some animating tricks. I had the idea to create a little tweenengine based on the Chain class. Currently it extends Chain. It uses lots of protected variables from the Chain class and overrides also some core functions for Chain to add the tween functions inside the process of Chain.

public class ChainTween extends Chain
{  
 function animate(properties:Object = null, duration:Number = 0, easing:Function = null):ChainTween
 {
   // do stuff
   return this;
 }
}

Now this is the problem: I want to keep the chaining syntax, but wait() returns a Chain instance and Chain has no animate function.

var chain:ChainTween = new ChainTween();
chain.wait(100).animate({x:200}, 100).wait(250);

I have tried to override the wait() and add() function in the ChainTween class but this causes an incompatible override.

I could cast chain.wait(100) as ChainTween, but this is very ugly and not useful when I am chaining lots of them. Now I don't want to add any of the ChainTween functions to Chain (no dummy functions too), and I want to keep completion to all functions, so returning Object is not an option too. I tried to use an interface, but this gives the same problem, since the functions of an interface should be implemented in the class that implements it.

Now I have thought about creating an instance of Chain inside ChainTween, but this does not allow me to override functions, and then I should make lots of properties public instead of protected, which is not preferred too.

Is this possible and does anyone has a great solution for this?

Mark Knol
  • 9,663
  • 3
  • 29
  • 44

6 Answers6

2

This problem is quite common. The design pattern you are using is called Fluent Interface and if you Google "Fluent Interface Inheritance", you'll find lots of questions and very few answers.

A common way to solve it in C#, Java and C++ is to use templates. However, I cannot tell how to implement the same in AS3, I found this topic that might help you.

Community
  • 1
  • 1
Vincent Mimoun-Prat
  • 28,208
  • 16
  • 81
  • 124
1

If you want function to be listed by code completion, it must be there. This rules out any runtime discovery methods. I would add to Chain something like this:

public function animate(args:Object, time:int):Chain {
    throw new Error("Animate is supported only on ChainTween");
}

to be overridden in ChainTween. Don't think it's such a big stretch.

alxx
  • 9,897
  • 4
  • 26
  • 41
  • Thanks, but this means if I have 10 tween functions, I should add them all to the Chain class? This gives the Chain class lots of functions which has nothing to do with its orginal 'function'. I hope there will be another solution. – Mark Knol Dec 17 '10 at 10:51
  • JQuery can do that, but it has no code completion... I don't see how to satisfy all requirements, something has to be sacrificed here. – alxx Dec 17 '10 at 12:34
1

Following the structure of the Chain class, it should be possible ( and somehow logical ) to use the add method to call the animate method... Not knowing more about the Chain class , it's difficult to be more accurate , but in theory it would seem possible... It would require adding a new argument to the add method.

var chain:ChainTween = new ChainTween();
var params:Object = {x:200};
chain.wait(100).add(animate, 300 , params).wait(300);

alxx has a point, it would seem that something has to give somehow, unlike Javascript, AS3 is a strongly typed language , this is the very cause of your limitations. If you need to implement methods as specific as rotate, fadeOut , you may not have a whole lot of solutions available. Those methods will either return a ChainTween, a Chain or an Object, and you're dismissing both Object and * ...

Somehow, I still think that adding the rotate , fadeOut or animate method with add() ( or any other method you may create for that purpose ) is more in line with Chain's design.

PatrickS
  • 9,539
  • 2
  • 27
  • 31
  • Yes you are right but it works a bit more complicate internally then this. Beside how it works, I want to create nice 'shorthand functions' like show(), hide(), fadeIn(), fadeOut(), rotate(), move() and also the animate() and I want them all to be chainable. – Mark Knol Dec 17 '10 at 14:42
0

You could try returning * instead of Chain but this removes code hinting.

Maurycy
  • 1,324
  • 1
  • 11
  • 33
  • True, but this in not what I want. I love + want code hinting :) – Mark Knol Dec 17 '10 at 10:59
  • 1
    Well aside from what the alxx suggested I think there is no other way to do this, as far as I can tell no flash IDE has such magic functionality, plus I guess it kind of goes against the principle of strong-typed language. Though I like the overall idea :). – Maurycy Dec 17 '10 at 11:01
0

if i were you i'd created an IChain interface describing only core functions (add, wait etc)

www0z0k
  • 4,444
  • 3
  • 27
  • 32
  • Yes, but how would you do this? Should all functions return IChain instead of Chain? I tried this, but then again this causes an error if I do chain.wait().animate(), because animate isn't there in the IChain interface. If I add all functions in the IChain, then the orginal Chain class should have all the interface functions and then Chain should implement the tween functions, which is not preferred. Maybe this is possible with multiple interfaces? – Mark Knol Dec 17 '10 at 12:04
  • @Mark Knol: if you are calling `instance.animate()` it means you know that `instance` is a `ChainTween` - so imho `IChain` needs only core functions and access to 'extended' functions should be gained like this: `(chain.wait() as ChainTween).animate()` – www0z0k Dec 17 '10 at 12:55
  • Yes, I know that, but it would be very difficult to explain this inside the documentation or to others since it also breaks the easy coding style which I am trying to use. So that is not really an option. – Mark Knol Dec 17 '10 at 14:45
0

I would go for the already suggested interface idea. Wait would return something like 'IChainTween' which only contains methods to configure a ChainTween and also a function like 'then' which returns the original Chain.

package
{
    public interface IChainTween
    {
        function doSomething():IChainTween;
        ...
        function then():IChain;
    }
}

package
{
    public class ChainTween implements IChainTween
    {
        private originalChain:IChain;
        public function ChainTween(IChain chain)
        {
            originalChain = chain;
        }
        ...
        public function doSomething():IChainTween
        {
            return this;
        }
        public function then():IChain
        {
            return originalChain;
        }
    }
}
David Rettenbacher
  • 5,088
  • 2
  • 36
  • 45