How much OOP_Crazy should real life programmer be?

98 views Asked by At

Given : Complex algorithm.

switch obj.field1 + "-" + obj.field2
case "A-A":
    return Sample(obj){Error = "Type can't be matched"}
case "A-B":
    {
    if(obj.field3 != null){
        var match = otherObjectCollection.FirstOrDefault(otherOb.field2 == obj.field3);
        return mathc != null? Sample(obj,match) : Sample(obj){Error="Can't find match"}
    }else{use field4...}
}
case "A-C":
{
var related = objectCollection.FirstOrDefault(parent.field4 == obj.field3);
if(related == null)
 return Sample(obj){Error = "Can't find parent"}
else {
  if(related.field3 != null){
       var match = otherObjectCollection.FirstOrDefault(otherOb.field2 == related.field3);
       return mathc != null? Samble(obj,match) : Sample(obj){Error="Can't find match"}
  }else{ use field 4 ...}

}
    }

And so on. A lot of tricky rules. I came up with next decision:

abstract class AggregationChain{
    protected MyObj Obj;
    abstract string Type{get;}
    abstract Priority Priority{get;}
    abstract bool Decide(MyObj obj);
    abstract Sample Aggregate(ICollection<MyObj> objects,ICollection<OtherObj> otherobjects);
    bool CanDecide(MyObj obj){
        if(Decide(obj)){
           Obj = obj;
           return true;
        }
        return false;
    }
 }

So now I can have ChainLinks for example:

 class ABAggregationChainLink{

        string Type{get{return "A-B"}}
        Priority Priority{get{return Priority.High}}
        bool Decide(MyObj obj){ 
           return obj.fiel2 != null;
        }
        Sample Aggregate(ICollection<MyObj> objects,ICollection<OtherObj> otherobjects){
            var match = OtherObjectCollection.FirstOrDefault(otherOb.field2 == obj.field3);
            return mathc != null? Samble(obj,match) : Sample(obj){Error="Can't find match"}
        }

     }

In this example i'll need to create one more A-B ChainLink that will handle "else" situation. And for all switch cases i'll need to create different ChainLinks. This of course increase number of classes and time for implementation but classes more suitable for unit testing and from my point of view more extandable and flexible.

Question :

  1. I'm thinking - may be I'm overexcited by Open-Close and "Good programming" and for real life application it is better just create method with switch and take care of reusable parts?
  2. I have filling that here may be better solution?

PS. This is not working C# code, I just tried to explain the main logic.

1

There are 1 answers

2
James On BEST ANSWER

When analysing something like this you can't just look at what it is now.

  • Is that algorithm easily maintainable going forward?
  • How easy is it to add new rules without changing existing functionality?
  • How easy is it to test?
  • Do you need to add new rules at runtime (plugin architectures)?

At 3 cases with some if elses, it probably doesn't really matter either way. At 40 cases with sub cases and a new requirement that it all needs unit testing? I'd prefer the clean isolation of a class per rule (or more likely a generic class with lambda expressions add to a collection of rules)

Your link class looks OK, but you'll still need the chain class to hold all the links together and work out which link to process. That could end up with a an structure to replace that switch statement like:

Dictionary<String, Dictionary<String, List<Link>>> index;

if (index.ContainsKey(obj.Field1))
{
   var subIndex = index[obj.Field1];
   if (subIndex.ContainsKey(obj.Field2))
   {
      var linkList = subIndex[obj.Field2];
      foreach(var Link in linkList)
      {
          if (Link.Decide(obj))
          {
              return Link.Aggregate(objects, otherObjects);
          } 
      }
   }
}

If you change Decides signature to Func<bool, MyObj> and Aggregate Func<Sample, ICollection<MyObj>,ICollection<OtherObj>>

then you can add rules like

RuleChain.AddRule("A", "A", (o) => true, (objs, others) => Sample(obj){Error = "Type can't be matched"})
RuleChain.AddRule("A", "B",...