Reputation: 81
Currently have to create a Decision Table that is based on different objects that can each have different/multiple information.
Example:
object A can be: OK / KO
object B can be: OK / WARNING / KO
object C can be: STARTING / RUNNING / ENDING
Depending on A, B and C's status we get a different output. I currently on did lots of IF statements and feel like it's very rookie-ish coding.
Like-so:
if (C == STARTED) {
if (A == OK) {
if (B == OK)
return "something";
if (B == WARNING)
return "something else";
else
return "something more";
}
...
}
Are there any known technics (using hasmaps or something already in Java 8) that handles this types of issues in a proper way?
Thought of implementing a decision tree but my current if statements take about 30 lines and not sure if it'd be wise to create a (bunch of) whole new class(es) to solve this.
Upvotes: 3
Views: 5530
Reputation: 12009
There's a number of approaches. If those states are enum values, integers or Strings, a switch can look a bit cleaner than lots of nested if
s, although it comes down to the same thing and can still end up with some deep nesting.
switch (C) {
case STARTED :
{
// do stuff for this state
}
case RUNNING :
{
// do stuff for this state
}
}
Don't forget break;
statements where necessary, or a case may fall through. It won't be necessary after a return statement, though.
A step further would be to simply break things up in methods.
switch (C) {
case STARTED : return handleStartedState(A, B);
case RUNNING : return handleRunningState(A, B);
}
You then implement the logic for each C
case in a separate method, which in turn does the same for other inputs. The downside of this is that you may end up with lots of methods with long names for various combinations of inputs.
Perhaps the better object-oriented approach would be to put the responsibility of how to handle the various combinations of states in the classes themselves. I don't know what your A
, B
and C
are, but if they're classes you control you could create methods in each that delegate as needed. For example, the C
class has a method handle(A, B)
that calls methods on A
depending on its own state, and A
then does the same with B
. A way to approach this would be the use of the state pattern, a design pattern that couples behavior to the state of some object.
This does imply you're creating some classes just to handle this matter, and it might be over-complicating something that simply does not need to be that complicated. Ask yourself these questions:
If any of those are true, you may consider a more complex approach. But if the few dozen lines of if
statements do the job just fine, and they're unlikely to change any time soon and changes aren't a refactoring nightmare, maybe they're the right tool for the job. While I value up-front design and looking at the future, it's easy to get tricked into going for a design that's too generic and ends up being a rocket ship when a bicycle would have sufficed. In other words, avoid being an "architecture astronaut". A well-known rule in development is to always start with the simplest thing that could possibly work. I don't consider it an absolute; if a problem starts expanding, refactoring can become difficult when some up-front design may have been ideal from the start. But often it's a good initial approach.
Also realize that patterns can provide an answer, but they themselves exist by grace of their environment: object-oriented programming. Java is the kingdom of nouns, of "things", when sometimes what you need are verbs, "actions". Functional programming allows for some things which require patterns in object-orientation to be done so naturally that you don't even realize there's a pattern to it. Java 8 has taken an important step towards functional programming, so you might want to look into how to chain behavior rather than classes.
Finally, for complex rule sets that tend to expand or change you'd want to look into a rules engine. As freedev pointed out in a comment, Drools is a Java solution for this.
The question has no "one size fits all" answer because it depends too much on the project, context, rest of the code base... So hopefully the above info sets you on your way to find what works best.
Upvotes: 2
Reputation: 131396
In fact you have three distinct cases here :
if (C == STARTED) {
if (A == OK) {
if (B == OK)
return "something";
if (B == WARNING)
return "something else";
else
return "something more";
}
...
}
Separate distinct cases in distinct methods or each one in a class allow to test it unitary, to understand easily each rules and to change it without side effect on other rules.
For example :
if (C == STARTED && (A == OK) && (B == OK)
is a rule
if (C == STARTED && (A == OK) && (B == WARNING)
is another rule
if (C == STARTED && (A == OK) && (B == KO)
is another rule
Either create a method for each one or create an interface with a processing method and make each rule a distinct implementation of it.
Chain of responsibility is a good pattern for this way.
For example you could have these.
Rule interface :
public interface IRule {
public void setNextRule(IRule nextRule);
public abstract boolean apply(Data data);
}
Abstract common class for rule interface :
public abstract class AbstractRule implements IRule {
protected IRule nextRule;
public void setNextRule(IRule nextRule) {
this.nextRule = nextRule;
}
public boolean applyNextRuleIfExist(Data data) {
if (this.nextRule != null) {
return this.nextRule.apply(data);
}
return false;
}
}
Concrete rule :
public class RuleXXX extends AbstractRule {
public boolean apply(Data data) {
if (data.C == STARTED && (data.A == OK) && (data.B == OK){
return true;
}
return applyNextRuleIfExist(inputDataForDiscountRules);
}
}
And at last you can create the rule chain and use it :
IRule firstRule = new RuleXXX();
firstRule.setNextRule(new RuleYYY());
...
// apply the chain
Data data = ...;
firstRule.apply(data);
Upvotes: 3