Search code examples
javascriptperformancereadability

How should I deal with multiple order-dependent conditions?


I have multiple concurrent conditions in which the order is dependent on the conditions. I've come up with multiple approaches to deal with this issue and I need help choosing the best possible solution out of several that I have prepared. Things to consider are: Performance, Re-usability, Readability, Memory Consumption.

Approach 1: define multiple nested switch cases

var collision = {
    detect1: function(subject, target){
        // multiple switch cases
        var shapes = {};
        shapes[subject.type] = subject;
        shapes[target.type] = target;

        switch(subject.type) {
            case 'rectangle':
                switch(target.type) {
                    case 'ellipse':
                        return this.rectWithEllipse(subject, target);
                }
                break;
            case 'ellipse':
                switch(target.type) {
                    case 'rectangle':
                        return this.rectWithEllipse(target, subject);
                }
        }
    },

Approach 2: Store types in an object registry and switch order based on parameter testing

    detect2: function(subject, target){
        // object registry and place switch
        var shapes = {};
        shapes[subject.type] = subject;
        shapes[target.type] = target;

        var shape1 = subject;
        var shape2 = target;

        var reverseShapeOrder = function() {
            shape2 = target;
            shape1 = subject;
        };

        if ( shapes.rectangle && shapes.ellipse ) {
            if (subject.type === 'ellipse') {
                reverseShapeOrder();
                return this.rectWithEllipse(shape1, shape2);
            }
        }
    },

Approach 3: concatenate types to a string and switch order based on indexOf testing for order.

    detect3: function(subject, target) {
        // string concat and decoding with place switch
        var shapeString = subject.type + target.type;

        var rectIndex =  shapeString.indexOf('rectangle');
        var ellipseIndex = shapeString.indexOf('ellipse');
        var pointIndex = shapeString.indexOf('point');

        var shape1 = subject;
        var shape2 = target;

        var reverseShapeOrder = function() {
            shape2 = target;
            shape1 = subject;
        };

        if (rectIndex && ellipseIndex) {
            if (ellipseIndex < rectIndex) {
                reverseShapeOrder();
            }
            return this.rectWithEllipse(shape1, shape2);
        }
    },

Approach 4: Standard traditional if-else statements

    // traditional logic
    detect4: function(subject, target) {
        if (subject.type === 'rectangle'  && target.type === 'ellipse') {
            return this.rectWithEllipse(subject, target);
        }
        else if (subject.type ==='ellipse' && target.type === 'rectangle') {
            return this.rectWithEllipse(target, subject);
        }
    },
    rectWithEllipse: function(rect, ellipse) {
        return false;
    }
};   

Approach 5: On the fly selector with reference function (thanks for the on-the-fly selector suggestion @Bergi)

detect5: function(subject, target) {
        return this[subject.type + '_with_' + target.type](subject, target);
    },
    rect_with_ellipse: function(rect, ellipse) {
        return false;
    },
    ellipse_with_rect: function(rect, ellipse) {
        this.rect_with_ellipse(ellipse, rect);
    }
};   

Please help me select the best solution and understand why it is best. Thanks

Keep in mind that the full list of combinations will be larger like so:

rectWithPoint: function(rect, point) {
    return false;
},
rectWithEllipse: function(rect, ellipse) {
    return false;
},
rectWithRect: function(rect, rect) {
    return false;
},
ellipseWithPoint: function(ellipse, point) {
    return false;
},
ellipseWithEllipse: function(ellipse, ellipse) {
    return false;
}

Solution

  • I would define a mapping as follows, which will make it easy to extend:

    targets: {
      rectangle: {
        point: function rectWithPoint(rect, point) {
          return false;
        },
        ellipse: function rectWithEllipse(rect, ellipse) {
          return false;
        },
        rectangle: function rectWithRect(rectLhs, rectRhs) {
          return false;
        }
      },
      ellipse: {
        point: function ellipseWithPoint(ellipse, point) {
          return false;
        },
        ellipse: function ellipseWithEllipse(ellipseLhs, ellipseRhs) {
          return false;
        }
      }
    },
    
    detect5: function (subject, target) {
      var tmp, candidates = this.targets[subject];
    
      if (!candidates) {
        tmp = subject;
        subject = target;
        target = tmp;
        candidates = this.targets[subject];
      }
    
      if (candidates && candidates.hasOwnProperty(target)) {
        return candidates[target].call(this, subject, target);
      }
    
      return false;
    }