3

I am creating a scoring system for a competition that is somewhat obscure, but it resembles the Olympics in terms of its high-level structure. Therefore, I will ask my question in terms of an abstracted, fictional version of the Olympics, since everyone can relate to that to some degree.

Assume the following hierarchy of classes and score rollups:

  • Event class has one or more Sports (e.g., the 2012 Olympics has Decathlon, IndividualMedley, etc.) and one or more EventScores (one EventScore per Team per Event calculated by adding up the team's SportScores for a particular Event)
  • Sport class has one or more Elements (e.g., Decathlon has Shotput, Javelin, etc.) and one or more SportScores (one SportScore per Team per Sport calculated by adding up the team's ElementScores for a particular Sport)
  • Element class has one or more Rounds (e.g., Shotput has Throw1, Throw2, etc.) and one or more ElementScores (one ElementScore per Team per Element calculated by adding up the team's RoundScores for a particular Element)

The scoring algorithm (let's call it the CalculateRoundScore() method) of a particular RoundScore depends on the Element to which the Round belongs. Examples:

  • Shotput element: CalculateRoundScore(double ThrowDuration, double ThrowDistance, double GruntVolume) { algorithm; }
  • Javelin element: CalculateRoundScore(double ThrowDuration, double ThrowDistance, double GruntVolume) { algorithm (different from Shotput algorithm even though parameters are the same, which would seem to eliminate overloading as a viable option); }
  • 100MeterDash element: CalculateRoundScore(double Time, bool FalseStart) { algorithm; }

Although I am seeking general architecture guidance, here are some of my specific questions:

  • How do I associate different CalculateRoundScore methods to different Elements?
  • Should I use interfaces, inheritance, overloading, or some other approach?
  • In which class(es) should the various CalculateRoundScore methods be located?

Conceptually, I can imagine each Element being an ICalculateRoundScore interface. However, that only makes sense if I create a different class for each different element, which doesn't seem right. It feels like I need some kind of a lookup in which the Round class picks its particular CalculateRoundScore method depending on the Element to which it belongs, but I'm not sure what would be the correct object-oriented way to do this.

  • How and where does your program produce the input parameters for the scoring functions? Do you run some kind of simulation or game subroutine for each discipline to create those values (so you have a different, complex logic for each discipline either)? Or does your program just process predefined values from some kind of file or data base (so there is no complex logic involved)? – Doc Brown Apr 24 '14 at 21:05
  • Depending on the particular element, the input parameters will come from one of two primary of sources. The first source is a stream of data being produced by automated data acquisition systems. The second source is a form that the event administrator completes for each particular "round." – MikeWazz Apr 24 '14 at 21:53
  • @Doc Brown - I'm not sure this appropriate (I'm new to stackexchange, so I don't know the etiquette yet), but I'd love to discuss the actual application with you in a lot more detail. So, if you're looking for work, I'd be interested in hiring you as a consultant/mentor (I'm a newbie C# programmer, so a lot of this is over my head). – MikeWazz Apr 24 '14 at 21:57
  • Thanks for the offer, but currently I am full-time employed. – Doc Brown Apr 25 '14 at 13:38
  • Bummer...you're really good at explaining things! – MikeWazz Apr 25 '14 at 16:43
4

However, that only makes sense if I create a different class for each different element, which doesn't seem right

To me, this feels perfectly right, since, it is just an application of the classic "strategy" pattern (in this case a RoundScoreCalculationStrategy). I would design this in a way like this:

 interface RoundScoreCalculationStrategy
 {
     double Calculcate(Measurements m);
 }

Strategy objects can easily be put into any kind of lookup table. Measurements will be a base type (or interface) for encapsulating the possible results of one round in one of your sport events (like the tuple ThrowDuration, ThrowDistance, GruntVolume) - so you can separate the supply of those values from the score calculation. You still have to construct different Measurement objects with different constructor parameters, but now you can

  • construct those objects at the place in code where you "run the round" (which can be a completely different place from where the scoring takes place)
  • reuse the same Measurement objects if they contain the same parameter tuples (for example, for Shotput and Javelin), even if the scoring calculation is a different one

Of course, you may consider to use delegate functions instead of different RoundScoreCalculationStrategy objects (which may result in less code if each of your score calculation algorithms is just a small function), but the main idea keeps the same.

One thing to add: maybe it is sufficient not to distinguish between the Strategy classes and the "Element" classes at all. Your different disciplines (which you called Elements above) could get a common virtual base class Discipline - (could be also an interface), where CalculateScore(Measurements m) is just a virtual method of that base class, and Shotput, Javelin etc. are subclasses of Discipline with different implementations of that method.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.