Yes, it's do-able, and may be a good idea
Certainly this is doable, and it can often yield clearer logic, depending on what parameters you are using. Hopefully the parameters are related in some way that ties into a well-understood business concept.
Identify the hidden business concept
In cases where the two parameters map to a single (hidden) business concept, you need to make that concept explicit, using a mapping function that takes p
and q
and outputs the case.
This example corresponds to your first example:
z = map(p, q)
switch (z)
case z1:
a();
break;
case z2:
b();
break;
case z3:
c();
break;
case z4:
d();
break;
}
Or for your second example:
z = map(p, q)
switch (z)
case z1:
a();
break;
case z2:
a();
b();
break;
case z3:
c();
break;
case z4:
//No operation
break;
}
What is the mapping function?
The mapping function can be as simple as combining p
and q
into a bitmask (if they are Booleans) or combining strings (if they are, for example, product and subproduct codes). This could be a stretch in some circumstances, but is a plain and obvious solution in other circumstances. For example, consider these questions that may appear on an application for health insurance:
- Are you married? Yes or no
- Do you have children? Yes or no
In this case, your company might offer four products (the hidden business concept): single, married, single with children, and married with children. In this sort of scenario it is perfectly justified to combine the user's answers into a virtual product (given by the mapping function) and choosing a logic path based on the product instead of the individual flags. In fact it is much better code, IMO.
Getting rid of the switch case
Switch/case is ugly. Another approach would continue to use the hidden business concept but in a cleaner code pattern:
IProduct z = ProductFactory.GetProduct(q, p);
z.Execute();
...where the factory could return one of four products, all of which implement IProduct
which contains a product-specific Execute
method that will execute a()
, b()
, c()
, and/or d()
as needed.
Won't the factory have the same (original) issue?
The factory could contain nested if
statements (bringing back the original problem) or it could use a lookup table for instantiation, like this:
class ProductFactory
{
private static readonly Dictionary<bool, Dictionary<bool, Func<IProduct>>> _products
= new Dictionary<bool, Dictionary<bool, Func<IProduct>>>();
static ProductFactory()
{
_products.Add(false, new Dictionary<bool, Func<IProduct>> {
{ true, () => new ProductSingleWithChildren() },
{ false, () => new ProductSingle() }
});
_products.Add(true, new Dictionary<bool, Func<IProduct>> {
{ true, () => new ProductMarriedWithChildren() },
{ false, () => new ProductMarried() }
});
}
public IProduct GetProduct(bool isMarried, bool hasChildren)
{
return _products[isMarried][hasChildren]();
}
}
...where _products
is a list of lists, each item containing a Func<IProduct>
that will instantiate the appropriate type.
Dictionaries with a key of bool
seem a little weird, so in an actual piece of code you might lean toward using an enum
instead, or possible a string
containing a code. I would suggest using an identifier that is aligned with the business/domain; make it easy for other developers to understand what is going on.
For completeness, here is an idea what the classes would look like:
interface IProduct
{
void Execute();
}
class ProductSingle : IProduct
{
public void Execute()
{
a();
}
}
class ProductSingleWithChildren : IProduct
{
public void Execute()
{
a();
b();
}
}
class ProductMarried: IProduct
{
public void Execute()
{
c();
}
}
class ProductMarriedWithChildren : IProduct
{
public void Execute()
{
//No operation
}
}
You'll notice in the end we not only got rid of the duplicate if
condition, there are in fact ZERO if
statements in the whole solution!!! :) Pretty neat, and will get you a low cyclomatic complexity score and a nice clean CPU pipeline. Plus much more readable code.
Then again, we just wrote a LOT more lines of code than the original example. So, depending on context, maybe the nested if
statements would be better, in certain cases, e.g. the combination of the flags is arbitrary or doesn't map to a logical concept within the business problem, or the extra readability isn't necessary for a certain small problem. It's up to you, of course.