1

Imagine you have a factory where products are carried through the manufacturing process by belts. Somewhere in the midst of this, it is necessary to split a stream of products from a single belt input out onto multiple belts based on some business logic. Perhaps they need to be split out by color with output belts for red, green and blue products. Perhaps it's some sort of simple load balancing so products need to be split out based on percentages to each output belt.

                  ________________
                 / ___Color=Red___...
    ____________/ /_______________
 ..._Products___  ____Color=Green_...
                \ \_______________
                 \____Color=Blue__...

Or

                  __________
                 / ___60%___...
    ____________/ /_________
 ..._Products___  ____20%___...
                \ \_________
                 \____20%___...

Specifically I'm trying to reverse engineer the routing functionality of ProModel. The routing methodology should be able to be changed at runtime, as should the routing properties of the individual output "belts" (say changing the color criteria). If the routing methodology is switched back to one that was previously defined, it remembers the settings that were used.

To keep my code generalized, the products are "Entity" objects, the split off belts are "Route" objects, the internal routing logic is a "RoutingRule" and the whole input belt/internal logic/output belts thing is a "Router". Based on ProModel's usage, the "RoutingRule" applies to the "Router" and not the individual "Routes" - if the rule is RouteByPercent then every outgoing "Route" should have a percent, whereas if it is RouteByAttribute, each "Route" would have an Attribute/Comparator/Criteria (e.g. "Color", "=", "Blue") set to test against incoming "Entities".

I can see that using the Strategy Pattern would allow flexibility to support and swap out "RoutingRule" objects (for example the list of possible rules in the ProModel link); however, I don't quite see how I can have the state information for each rule stored without tightly coupling the "RoutingRule" and the "Route" objects.

When applying the Strategy Pattern to this case, how should I store RoutingRule-specific attributes that apply per Route? I'm trying to think through my options and I don't like any of them:

  1. The naive way would be to have member variables in the Route class for all possible RoutingRules: m_Percent, m_EntityAttribute/m_EntityComparator/m_EntityCriteria ... but that would be horrible for obvious reasons. Any time I added a new kind of RoutingRule, I'd have to add member variables to the Route class.
  2. The way I had been planning on doing it was with an associative array (Dictionary or custom HashTable) so attributes could be added or removed on the fly. This is generally fine, but there has to be some code to a) initialize the Rule-specific attributes b) tell me which attributes need to be set for the current rule (I can't just prompt the user for all the Route attributes because they may not all be used by the currently selected rule) c) validate that the Route attributes are in a consistent state.
  3. The only other way I can think of is to have the Route attributes stored inside the RoutingRule itself, an associative array inside an associative array so I can look up the Route by name and get a list of the exact attributes which apply per Route for the current rule. This way decouples the most, but then I'm not sure how to structure it so that asking a Route about its properties is a transparent process. Instead of Route.Attributes(strAttrName) like #1 and #2, it would be more like RoutingRule.RouteAttributes(Route.Name).Attributes(strAttrName).
  4. Maybe I'm overthinking this?

Incidentally, I'm doing this in VBA with all the limits that implies.

Blackhawk
  • 5,984
  • 4
  • 27
  • 56
  • 2
    I'm interested, but what's the question @Blackhawk? – RubberDuck Jun 03 '14 at 22:42
  • @ckuhn203 See if my edit from the bolded text down helps clarify my confusion :( – Blackhawk Jun 04 '14 at 14:37
  • @Blackhawk I am not sure as this is quite twisted already but maybe [**this solution**](http://stackoverflow.com/questions/20489594/data-structure-which-needs-to-be-filled-in-vba/20664702#20664702) can give an idea how to iterate attributes (no.3) on your list? –  Jun 04 '14 at 15:28

1 Answers1

1

Check this skeleton written in some language:

class Entity { // Or call this the EntityHolder
  Map attrs;
  RealEntity re; // The payload
}

abstract class RouteElement {
  void process(Entity entity);
  Map attrs;
}

interface RouterRule {
  RouteElement pickNextRouteElement(Entity e, List<RouteElement> allOutRoutes);
}

class Router extends RouteElement {
  List<RouteElement> outRoutes;
  RouteElement deadRoute;
  RouterRule routerRule;

  void process(Entity entity) {
    RouteElement nextRouteElement = routerRule.pickNextRouteElement(entity, outRoutes);

    if (nextRouteElement == null) {
      if (deadRoute) {
        deadRoute.process(entity);
      }
    } else {
      nextRouteElement.process(entity);
    }
  }

  void setRouterRule(RouterRule rr) {
    routerRule = rr;
  }

  RouterRule getRouterRule() {
    return routerRule;
  }

  void addOutRouteElement(RouteElement re) {
    outRoutes.add(re);
  }

  void setDeadRoute(RouteElement re) {
    deadRoute = re;
  }
}

class ColorRouterRule implements RouterRule {

  RouteElement pickNextRouteElement(Entity e, List<RouteElement> allOutRoutes) {
    foreach(RouteElement re in allOutRoutes) {
      if (e.getAttr("color") == re.getAttr("color") {
    return re;
      }
    }

    return null;
  }
}

class RandomRouterRule implements RouterRule {

  RouteElement pickNextRouteElement(Entity e, List<RouteElement> allOutRoutes) {
    int rand = random(0, allOutRoutes.length());
    return allOutRoutes.get(rand);
  }
}

class PercentRouterRule implements RouterRule {
  RouteElement pickNextRouteElement(Entity e, List<RouteElement> allOutRoutes) {
    int[] weights = int[allOutRoutes.length()];

    int prevWeight = 0;
    for (i = 0; i < weights, i++) {
      weights[i] = prevWeight + allOutRoutes.get(i).getAttr("percent");
      prevWeight = weights[i];
    }

    int rand = random(0, prevWeight);

    for (i = 0; i < weights, i++) {
      if (rand <= weights[i]) {
        return allOutRoutes[i];
      }
    }
  }
}

class RouterManager {

  Router routerBeingManaged;
  Stack<RouterRule> oldRouterRules;

  void setNewRouterRule(RouterRule rr) {
    oldRouterRules.push(routerBeingManaged.getRouterRule());
    routerBeingManaged.setRouterRule(rr);
  }

  void revertToOldRouterRule() {
    routerBeingManaged.setRouterRule(ooldRouterRules.pop());
  }

  ..
}


class Route extends RouteElement {
  List<RouteElement> route;

  void addRouteElement(..) {
    ..
  }

  void process(Entity entity) {
    foreach(RouteElement as re in route) {
      re.process(entity);
    }
  }
}

class MyApp {

  void main() {
    Route r = new Route();

    // Build the route
    r.addRouteElement(...);
    r.addRouteElement(...);

    Router router1 = new Router();
    router1.addOutRouteElement(..);
    router1.addOutRouteElement(..);
    router1.addOutRouteElement(..);

    RouterManager router1Mgr = new RouterManager(router1);
    router1Mgr.setRouterRule(new RandomRouterRule());

    r.addRouteElement(router1);

    r.addRouteElement(...);
    r.addRouteElement(...);

    // Get entities from somewhere
    r.process(entity);

    // Change the router rule
    router1Mgr.setRouterRule(new PercentRouterRule());

    // Get entities from somewhere
    r.process(entity);

    // Revert the router rule
    router1Mgr.revertToOldRouterRule();

    // Get entities from somewhere
    r.process(entity);
  }
}
  • +1 Thanks for taking the time to type this up! This appears most similar to #2 on the list of options I am considering, with the addition of a mechanism for saving and restoring previous RoutingRules. The responisibility for populating the correct Entity attributes is still outside of the RoutingRule class, so anyone using the API would have to "just know" which Route attributes to populate, and any user interface for changing the current RoutingRule and the associated Route attributes (see the link in my question) would have to refer to a table associating RoutingRules and Route attributes. – Blackhawk Jun 05 '14 at 13:58
  • Entity attributes being populated by a RoutingRule suggests that such attributes are not intrinsic attributes of the Entity. Instead, RoutingRules should only route based on existing attributes of Entities or Routes. If your need is to add routing attributes to Entities (source routing), consider adding two RouteElements simultaneously where the UI MAY give the impression of adding just one. Here, one is a Router. Some elements before the router, you add the RouteElement that determines and adds the needed attributes to the Entities. For clarity, your UI may allow adding the two explicitly. –  Jun 05 '14 at 17:15
  • Sorry, I meant to say Route attributes and not Entity attributes there :P I think this is the implementation method I will go with. Thanks very much for your advice! – Blackhawk Jun 05 '14 at 21:46
  • In that case as you have identified the attributes are not intrinsic to the Routes but to the association between Router and Routes (in my terminology above RouteElement). You can correctly code such attributes to be held by the RoutingRule (reverting will also work then). And your RoutingRule should use such attributes in its logic to determine the destination of accepted Entities. So think like this: A RoutingRule will route based on the attributes of Entities, next RouteElements and Router<->next RouteElement association attributes. –  Jun 06 '14 at 06:15
  • Add a method (or methods) to the RoutingRule to return what attributes of the associations it uses for routing. I mean return meta data (like names of attributes, their types). Use them to dynamically add input widgets to the UI where the widget count will also depend on the number of next RouteElements a Router is connected to. As the user enters and saves association attributes, pack them in a collection of objects (of a new type like RouterRouteElementAssociationAttrs) and add to the instance of RoutingRule. –  Jun 06 '14 at 06:23