OOP problem: Extending a class, override functions

2019-05-11 05:42发布

问题:

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?

回答1:

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.



回答2:

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.



回答3:

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.



回答4:

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



回答5:

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



回答6:

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;
        }
    }
}