views:

359

answers:

5

I'm coding up some delicious business logic and I've come to a situation like this:

There are a bunch of rules (ruleA, ruleB, etc...) which all follow a standard pattern. I've created a Rule interface, and implemented it in RuleA, RuleB, and so on.

So now I just need a way to load them.

Right now I'm using a map:

var rules:Object = {
    ruleA: new RuleA(),
    ruleB: new RuleB(),
   ...
};

Then calling it like this:

function doStuff(whichRule:String, someData:Object):* {
    return rules[whichRule].applyTo(someData);
}

Now, the rules follow a standard naming scheme, and they are all part of one package (foo.rules)... Is there any way to load the rules without keeping them some sort of list?

Thanks!

Edit: To clarify, would like to discover the possible rules at runtime so I don't need to maintain a list of rules. For example:

function loadRule(name:String):Rule {
    import foo.rules;
    var rule:Class = foo.rules[name];
    return new rule();
}

Except, of course, I can't index into the foo.rules namespace... But that's what I'd like to be able to do.

A: 

This looks like a good candidate for some kind of Factory Patteren implementation to get the object you want. Something like:

public class RuleFactory
{
   public function create(ruleID : String) : IRule
   {
      switch (ruleID)
      {
         case "ruleA":
            new RuleA();
            break;
         case "ruleB":
            new RuleB();
            break;
      }
   }

}

then you call it with something like

function doStuff(whichRule:String, someData:Object):* 
{
   var ruleFactory : RuleFactory = new RuleFactory();
   var rule : IRule = ruleFactory.create(whichRule);

   return rule.applyTo(someData);
}

Some might say it's overkill but this is probably the approach i'd take as it's a concise and cohesive abstraction that makes sense to the majority of developers out there.

James Hay
Hrm, I guess I wasn't quite specific enough: I want to discover the rules at runtime so I don't need to keep a list of them anywhere.
David Wolever
hmm sorry about that... not really sure what you are trying to achieve. The rules have to be listed somewhere???
James Hay
They are – on the filesystem :PBut, seriously, each class is part of the `foo.rules` package. Maybe I can't assume that that's enough?
David Wolever
ahhh i get you. I don't actually think it's possible as you don't really have much of a reflection framework in actionscript (not that i know i you can reflect at the package level in .net either). You could try looking at what you get back from the flash.utils.decribeType() public method. Not sure it'd turn up anything on the package level http://livedocs.adobe.com/flex/3/langref/flash/utils/package.html#describeType()
James Hay
as3commons reflection library is great. SpiceLib has some nice reflection utilities also.
Joel Hooks
+2  A: 

You can get a reference to a class from a string with the flash.utils.getDefinitionByName(className);

For example

def getRule(someValue: String) : Class{
    return getDefinitionByName("mybusiness.rules.Rule" + someValue);
}

In the example above you are returning a class, but you can also instantiate it and return the object.

Note however that this will only work if the class, identified by the class name is compiled into your swf. If no where else in your code you refer to that class, or else it won't be compiled. This is usually achieved creating references in some other piece of code or your build system can pass that to the compiler.

Do note, that functions are first class objects in AS3, so, depending on your use case, you can define each rule as a function to a Rule class and pass that:

def getRule(someValue: String) : Function{
    return new Rule()["theRuleName"];
}

That depends on how much logic you'd like to put into those, and how much common code there is between those rules, of course.

Arthur Debert
To get the definitions into your file, something as simple as importing the package, and then declaring an array with the class names in it will work. var a:Array = [ClassA, ClassB, ClassC]. It's hacky but it works.
Alex Jillard
Thanks for the answer – that's what I was looking for.You make a good point, though, that they won't be built unless I reference them from somewhere else (I'm sure that could be worked around, but it would add more complexity...)... So I think you and back2dos have me convinced that I should stick with how I'm doin' it now. Thanks!
David Wolever
+3  A: 

i am sorry to say so, but what you want to do, is simply impossible ... it is not so much about package-level introspection ... if there were a way, it still would not be possible ...

the actual problem is:

all ActionScript compilers always only compile the main class and dependancies ... thus, if your Rule implementations never are mentioned anywhere, they will not be compiled into the resulting swf ... in consequence, they will not be available at runtime at all ...

the closest thing to what you want to do, would probably be to write a build script, that traverses your packages, and - following some rule - registers the classes using some automatically generated code ... the least you need, is an Array literal, containing all the classes

[Rule1, Rule2,...RuleN]

as simple as that ... relying on the fact that all rules are in the same package (which i find an awful restriction), you could then use getDefinitionByName to get the right class ...

IMHO however, apart from not being easy to accomplish, the approach is also bad style ... it's quite hackish, and imposes way too much ...

the map approach is pretty much the best you can get ... by using the map, the classes appear in you code and are thus compiled, and it's a clean way for a lookup ... also, you have more influence ...

you could create rules like this:

class NameIsRule implements IRule {
    private var _name:String;
    public function NameIsRule(name:String) {
     this._name = name;
    }
    public function applyTo(target:Object):* {
     return target.hasOwnProperty("name") && (target.name == this._name)
    }
}

and then have

rules = {
    isCalledJim : new NameIsRule("Jim"),
    isCalledJohn : new NameIsRule("John")
}

(stupid example, but i hope it illustrates the point)

and you might wanna wrap that into a class, to be cleaner ...

package {
    public class RuleMap {
     private var _rules:Object = {}
     public function RuleMap(init:Object = null) {
      for (var name:String in init) 
       this.addRule(name, init[name]);
     }
     public function addRule(id:String, rule:IRule):IRule {
      if (this.ruleExists(id))
       throw new Error("duplicate rule for id " + id);
      if (rule == nul)
       throw new ArgumentError("invalid value null for parameter rule");
      this._rules[id] = rule;
      return rule;
     }
     public function applyRule(id:String, target:Object):* {
      return this.getRule(id).applyTo(target);
     }
     public function getRule(id:String):IRule {
      if (this.ruleExists(id))
       return this._rules[id];
      else 
       throw new Error("no rule for id " + id);
     }
     public function ruleExists(id:String):Boolean {
      return this._rules.hasOwnProperty(id);
     }
    }
}

this is a little more verbous (well, you can always pass in an object to the constructor), but it is much safer, throws more reasonable values, and is faster, since AVM2 is faster when executing strictly typed code (in contrast to dynamic code, you actually make use of the interface ... it is quite pointless to declare an interface, if none of your code relies on it) ...

also, you should take the time, to create constants for the rules, instead of having tons of string literals all over you code ... this will help against typos etc. ...

when working with sealed classes in AS3, as you do, untyped coding does not offer any benefits, because it makes only real sense with ducktyped languages ... and AS3 is not ... in strict languages, keep strict, and the compiler will do a lot of work for you, and save you a lot of debugging time ...

i like the idea behind the Rules (nice, clean and flexible), but IMHO, you should rather be more exact than lazy at that Point ... i'd guess, this is something rather at the heart of your App ... so let it be rock solid ... ;)

back2dos
Thanks back2dos. You and Arthur have me convinced that, while I *could* do what I originally wanted, it would probably be more trouble than it's worth.
David Wolever
A: 

Here are some ideas:

  • have a single configurable rule Class that is configured via external XML/JSON or other such format that would define its properties.

  • write your rules in javascript and use ExternalInterface.

  • package your rules in SWFs as modules, and load them dynamically.

Joel Hooks
Thanks for the suggestions, but I feel like they would just add to the complexity I was initially trying to avoid.
David Wolever
A: 

Have you tried writing a text-to-rules parser and a simple rules engine instead of separate rule classes? I haven't seen a rules engine written in Actionscript (except the proprietary and somewhat gross port of Ruleby that I threw together once) but coming up with a pretty syntax is more difficult that coming up with a naive implementation of one.

JMHNilbog
Thanks for the suggestion, but that would add a lot of unnecessary complexity: it would be better for me just to list them, like I'm doing in the example.
David Wolever