Context
Suppose you have a component with a great many options to modify its behavior. Think a table of data with some sorting, filtering, paging, etc. The options could then be isFilterable, isSortable, defaultSortingKey, etc etc. Of course there will be a parameter object to encapsulate all of these, let's call it TableConfiguration. Of course we don't want to have a huge constructor, or a set of telescopic constructors, so we use a builder, TableConfigurationBuilder. The example usage could be:
TableConfiguration config = new TableConfigurationBuilder().sortable().filterable().build();
So far so good, a ton of SO questions deals with this already.
Moving forward
There is now a ton of Tables and each of them uses its own TableConfiguration. However, not all of the "configuration space" is used uniformly: let's say most of the tables is filterable, and most of those are paginated. Let's say, there are only 20 different combinations of configuration options that make sense and are actually used. In line with the DRY principle, these 20 combinations live in methods like these:
public TableConfiguration createFilterable() {
return new TableConfigurationBuilder().filterable().build();
}
public TableConfiguration createFilterableSortable() {
return new TableConfigurationBuilder().filterable().sortable().build();
}
Question
How to manage these 20 methods, so that developers adding new tables can easily find the configuration combination they need, or add a new one if it does not exist yet?
All of the above I use already, and it works reasonably well if I have an existing table to copy-paste ("it's exactly like Customers"). However, every time something out of the ordinary is required, it's hard to figure out:
Is there a method doing exactly what I want? (problem A)
If not, which one is the closest one to start from? (problem B)
I tried to give the methods some very descriptive names to express what configuration options are being built in inside, but it does not scale really well...
Edit
While thinking about the great answers below, one more thing occurred to me:
Bonus points for grouping tables with the same configuration in a type-safe way. In other words, while looking at a table, it should be possible to find all its "twins" by something like go to definition and find all references.
I think that if you are already using the builder pattern, then sticking to the builder pattern would be the best approach. There's no gaining in having methods or an enum to build the most frequently used TableConfiguration.
You have a valid point regarding DRY, though. Why setting the most common flags to almost every builder, in many different places?
So, you would be needing to encapsulate the setting of the most common flags (to not repeat yourself), while still allowing to set extra flags over this common base. Besides, you also need to support special cases. In your example, you mention that most tables are filterable and paginated.
So, while the builder pattern gives you flexibility, it makes you repeat the most common settings. Why not making specialized default builders that set the most common flags for you? These would still allow you to set extra flags. And for special cases, you could use the builder pattern the old-fashioned way.
Code for an abstract builder that defines all settings and builds the actual object could look something like this:
public abstract class AbstractTableConfigurationBuilder
<T extends AbstractTableConfigurationBuilder<T>> {
public T filterable() {
// set filterable flag
return (T) this;
}
public T paginated() {
// set paginated flag
return (T) this;
}
public T sortable() {
// set sortable flag
return (T) this;
}
public T withVeryStrangeSetting() {
// set very strange setting flag
return (T) this;
}
// TODO add all possible settings here
public TableConfiguration build() {
// build object with all settings and return it
}
}
And this would be the base builder, which does nothing:
public class BaseTableConfigurationBuilder
extends AbstractTableConfigurationBuilder<BaseTableConfigurationBuilder> {
}
Inclusion of a BaseTableConfigurationBuilder is meant to avoid using generics in the code that uses the builder.
Then, you could have specialized builders:
public class FilterableTableConfigurationBuilder
extends AbstractTableConfigurationBuilder<FilterableTableConfigurationBuilder> {
public FilterableTableConfigurationBuilder() {
super();
this.filterable();
}
}
public class FilterablePaginatedTableConfigurationBuilder
extends FilterableTableConfigurationBuilder {
public FilterablePaginatedTableConfigurationBuilder() {
super();
this.paginated();
}
}
public class SortablePaginatedTableConfigurationBuilder
extends AbstractTableConfigurationBuilder
<SortablePaginatedTableConfigurationBuilder> {
public SortablePaginatedTableConfigurationBuilder() {
super();
this.sortable().paginated();
}
}
The idea is that you have builders that set the most common combinations of flags. You could create a hierarchy or have no inheritance relation between them, your call.
Then, you could use your builders to create all combinations, without repeting yourself. For example, this would create a filterable and paginated table configuration:
TableConfiguration config =
new FilterablePaginatedTableConfigurationBuilder()
.build();
And if you want your TableConfiguration to be filterable, paginated and also sortable:
TableConfiguration config =
new FilterablePaginatedTableConfigurationBuilder()
.sortable()
.build();
And a special table configuration with a very strange setting that is also sortable:
TableConfiguration config =
new BaseTableConfigurationBuilder()
.withVeryStrangeSetting()
.sortable()
.build();
I would remove your convenience methods that call several methods of the builder. The whole point of a fluent builder like this is that you don't need to create 20 something methods for all acceptable combinations.
Is there a method doing exactly what I want? (problem A)
Yes, the method that does what you want is the new TableConfigurationBuilder(). Btw, I think it's cleaner to make the builder constructor package private and make it accessible via a static method in TableConfiguration, then you can simply call TableConfiguration.builder().
If not, which one is the closest one to start from? (problem B)
If you already have an instance of TableConfiguration or TableConfigurationBuilder it may be nice to pass it into the builder such that it becomes preconfigured based on the existing instance. This allows you to do something like:
TableConfiguration.builder(existingTableConfig).sortable(false).build()
If almost all configuration options are booleans, then you may OR them together:
public static int SORTABLE = 0x1;
public static int FILTERABLE = 0x2;
public static int PAGEABLE = 0x4;
public TableConfiguration createTable(int options, String sortingKey) {
TableConfigurationBuilder builder = new TableConfigurationBuilder();
if (options & SORTABLE != 0) {
builder.sortable();
}
if (options & FILTERABLE != 0) {
builder.filterable();
}
if (options & PAGEABLE != 0) {
builder.pageable();
}
if (sortingKey != null) {
builder.sortable();
builder.setSortingKey(sortingKey);
}
return builder.build();
}
Now table creation doesn't look so ugly:
TableConfiguration conf1 = createTable(SORTEABLE|FILTERABLE, "PhoneNumber");
How about having a configuration string? This way, you could encode the table settings in a succinct, yet still readable way.
As an example, that sets the table to be sortable and read-only:
defaultTable().set("sr");
In a way, these strings resemble the command-line interface.
This could be applicable to other scenarios that support the table re-use. Having a method that creates the Customers table, we can alter it in a consistent way:
customersTable().unset("asd").set("qwe");
Possibly, this DSL could be even improved by providing a delimiter character, that would separate the set and unset operations. The previous sample would then look as follows:
customersTable().alter("asd|qwe");
Furthermore, these configuration strings could be loaded from files, allowing the application to be configurable without recompilation.
As for helping a new developer, I can see the benefit in a nicely separated subproblem that can be easily documented.
What I would have done, if I didn't know SO
Assumption: There probably is way less than 2^(# of config flags) reasonable configurations for the table.
Figure out all the configuration combinations that are currently used.
Draw a chart or whatever, find clusters.
Find outliers and think very hard why they don't fit into those clusters: is that really a special case, or an omission, or just laziness (no one implemented full text search for this table yet)?
Pick the clusters, think hard about them, package them as methods with descriptive names and use them from now on.
This solves problem A: which one to use? Well, there is only a handful of options now. And problem B as well: if I want something special? No, you most probably don't.
Related
I'm not sure how to name this question but I tried my best. We have a table in the database with rows representing mappings, each row has a string column that specify what object corresponds to. For example some rows have 'Books' in that column, others have 'CDs', other have 'Paintings' etc.
We want to retrieve all rows associated to a specific kind of object, for example all rows associated with 'Books'. We have a class that serves that table.
We have two ways to write that class. Which option is better? (some Option 3 maybe? There is no preferred way? It doesn't matter? Genuinely curious.)
Option 1
public TableClass {
public List<Field> getBooksFields() {
return getFields('Books');
}
public List<Field> getCDsFields() {
return getFields('CDs');
}
...etc...
private List<Field> getFields(String name_of_field) {
return tableRepository.getRowsWithType(name_of_field);
}
}
And then everywhere in the code where we want to get the fields associated to books, we can just do:
List<Field> bookFields = tableClass.getBooksFields();
Option 2
public TableClass {
public List<Field> getFieldsWithType(String name_of_field) {
return tableRepository.getRowsWithType(name_of_field);
}
...
}
And then hardcode in a Constants static class, the name associated to each field in the database. So we would do:
import Constants.BOOKS_FIELD_NAME;
...
List<Field> bookFields = tableClass.getFields(BOOKS_FIELD_NAME);
Which option is better from a coding perspective??
My short answer to this question would be - Whatever you like the most. Because you call one more function, even though you won't (always) have a big impact on performance, there is some impact.
Mostly compiler will just inline your second function but if this doesn't happen then you are losing some performance. But I need to say that this is not that much and you won't even notice it if your code is nicely written.
What you want is to write code that is understandable and clean for everyone using it later and there is one other question here. Are there possibilities that your database query would need changes in keywords like 'Books'? It is always easier to change this in one place (1st option) than going through code and changing it more than once (search and replace - yes but still takes more time).
It is really up to you, both codes are nice. The second one is cleaner and easier to edit but the first one is more optimized (not that much).
More about calling functions from functions you can read here in this answer: https://stackoverflow.com/a/27875180/14759470
I am in the process of making an application which is meant to be a personal pet project of mine, designed around comparing two vehicles against 1 another from a computer game I play
the vehicles have a bunch of stats such as speed, healthpoints, turret traversal etc. and I want to create a small app that will highlight the differences between the vehicles, however i have come to a stumbling block where the arguments taken for the constructors for each vehicles are huge and difficult to read
Here is an example of object creation with the given constructor:
HeavyTank T110E5 = new HeavyTank("T110E5", 2200,54.56d, 875, 37, 30, 254,76,38, 203,127,70,300, 202, 6,32, 400,745,10);
I am sure you would guess that this application I am making, is a tank comparer based off World of tanks, where I am hard coding the tank stats, but as you can see, the arguments taken are difficult to read, making it difficult to create new objects without getting confused. Each tank has different stats so this means I would have to hard code close to 100+ tanks individually. If someone here has a solution of reducing the mess, or recommendations I am willing to listen.
I would also like to reinstate a point I made up at the top, this application is not for commercial purposes and is purely just a pet personal project of mine.
I would suggest storing your tank data in a simple file format, for example a CSV file with one line per tank. Then your tank object can take something like an InputStream as a parameter and stream in the tank details from the file.
And if you use a CSV file you can just use a typical spreadsheet program to edit your tank details quite easily.
This sounds like a perfect use case for the builder pattern.
There are lots of good examples on how to implement this. Here's how Google uses it in the CacheBuilder class of Guava:
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(10000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.removalListener(MY_LISTENER)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) throws AnyException {
return createExpensiveGraph(key);
}
});
This particular example even uses method chaining, but that's less important. The main feature is that newBuilder() sets some default parameters which you can then selectively adjust with one or more method calls. Finally, you call build() to actually create the instance (and calling the actual constructor with the long list of parameters).
JavaDoc + good IDE will help you figure out which arguments you need to provide.
Otherwise, you could wrap the arguments in a single object and pass that to the constructor. An overloaded constructor would work well, because you could unpack the arguments and pass it to the original.
If you know the arguments in advance, you would always put them in a Properties file and read them using java.util.Properties.
Use a fluent interface. Though generally used outside the ccnstructor, why not partly inside.
You might consider putting that information in a properties file that you could then easily edit. Your constructor would then take the name of the properties file and have the additional job of enforcing completeness and validation. While I still favor strongly-typed constructors, a property file approach might be easier to use in the long run.
If you have a mix of required and optional arguments (with sensible defaults), you can use the builder pattern. An example from Effective Java:
public class NutritionalFacts {
private int sodium;
private int fat;
private int carbo;
public static class Builder {
private int sodium;
private int fat;
private int carbo;
public Builder(int s) {
this.sodium = s;
}
public Builder fat(int f) {
this.fat = f;
return this;
}
public Builder carbo(int c) {
this.carbo = c;
return this;
}
public NutritionalFacts build() {
return new NutritionalFacts(this);
}
}
private NutritionalFacts(Builder b) {
this.sodium = b.sodium;
this.fat = b.fat;
this.carbo = b.carbo;
}
}
Then you can do things like:
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8). calories(100).sodium(35).carbohydrate(27).build();
The first two arguments are required (240 and 8). The other attributes are optional and can be supplied through aptly-named methods.
You can still use the builder pattern if you have a large number of required parameters; you can check that they have been set in the build() method.
I recommend implementing a Standard Constructor without any Parameters. Setters and Getters can be generated via eclipse. This helps keeping a look at all and adding or removing members.
The next thing you should do is to write all your vehicle stuff in a XML file. The tags help you with the vehicle names, values etc. Also adding and removing vehicles will be easier.
You can access XML files via JDOM. Just read the vehicle tags and call the setter methods with the values from the XML file. This can be done with a loop.
In the end, the only thing you have to take care of is your vehicle XML file.
Feel free to ask if there are any questions.
You have three reasonable options that I see:
1) Use the standard contructor("string", value, "string", value,...) format.
This is likely more effort than you will want to put into this program.
2) Use a constructor() method and then setPropertyName(value) methods for each property.
This will allow you to easily add them, but you still need to do it by hand.
3) Read the data in from a file.
Setup the file using columns with names (to avoid just moving the "I forgot what value comes next" problem to a different place) This should be the best.
My suggestion would be to create an object to hold these types with a specific parameter list upon initialization. You can divide up that object based on what types go together logically. Then you'll only need two or three constructors with five parameters each. For example:
HeavyTank T110E5 = new HeavyTank("T110E5", 2200,54.56d, 875, 37, 30, 254,76,38, 203,127,70,300, 202, 6,32, 400,745,10);
would become:
StatObject1 stat1 = new StatObject1(2200, 54.56d);
StatObject2 stat2 = new StatObject2(875, 37, 30);
StatObject3 stat3 = new StatObject3(254, 76, 38);
...
HeavyTank T110E5 = new HeavyTank("T110E5", stat1, stat2, stat3, ...);
or however that works out logically for your system. The benefit of this is that you can apply this to any other tank you create, and can create modifiers in every StatObject class specific to that data.
I am using the Builder pattern to make it easier to create objects. However, the standard builder pattern examples do not include error-checking, which are needed in my code. For example, the accessibility and demandMean arrays in the Simulator object should have the same length. A brief framework of the code is shown below:
public class Simulator {
double[] accessibility;
double[] demandMean;
// Constructor obmitted for brevity
public static class Builder {
private double[] _accessibility;
private double[] _demandMean;
public Builder accessibility(double[] accessibility) {
_accessibility = accessiblity.clone();
return this;
}
public Builder demandMean(double[] demandMean) {
_demandMean = demandMean.clone();
return this;
}
// build() method obmitted for brevity
}
}
As another example, in a promotion optimization problem, there are various promotional vehicles (e.g. flyers, displays) and promotion modes, which are a set of promotional vehicles (e.g. none, flyer only, display only, flyer and display). When I create the Problem, I have to define the set of vehicles available, and check that the promotion modes use a subset of these vehicles and not some other unavailable vehicles, as well as that the promotion modes are not identical (e.g. there aren't two promo modes that are both "flyer only"). A brief framework of the code is shown below:
public class Problem {
Set<Vehicle> vehicles;
Set<PromoMode> promoModes;
public static class Builder {
Set<Vehicle> _vehicles;
Set<PromoMode> _promoModes;
}
}
public class PromoMode {
Set<Vehicle> vehiclesUsed;
}
My questions are the following:
Is there a standard approach to address such a situation?
Should the error checking be done in the constructor or in the builder when the build() method is called?
Why is this the "right" approach?
When you need invariants to hold while creating an object then stop construction if any parameter violates the invariants. This is also a fail-fast approach.
The builder pattern helps creating an object when you have a large number of parameters.
That does not mean that you don't do error checking.
Just throw an appropriate RuntimeException as soon as a parameter violates the objects invariants
You should use the constructor, since that follows the Single Responsibility Principle better. It is not the responsibility of the Builder to check invariants. It's only real job is to collect the data needed to build the object.
Also, if you decide to change the class later to have public constructors, you don't have to move that code.
You definitely shouldn't check invariants in setter methods. This has several benefits:
* You only need to do checking ONCE
* In cases such as your code, you CAN'T check your invariants earlier, since you're adding your two arrays at different times. You don't know what order your users are going to add them, so you don't know which method should run the check.
Unless a setter in your builder does some intense calculations (which is rarely the case - generally, if there's some sort of calculation required, it should happen in the constructor anyway), it doesn't help very much to 'fail early' in, especially since fluent Builders like yours use only 1 line of code to build the object anyway, so any try block would surround that whole line either way.
The "right" approach really depends on the situation - if it is invalid to construct the arrays with different sizes, i'd say it's better to do the handling in the construction, the sooner an invalid state is caught the better.
Now, if you for instance can change the arrays and put in a different one - then it might be better to do it when calling them.
I've been developing a massive Role Playing Game. The problem is that I'm having trouble engineering how will I manage the Item and Inventory system. Currently I have something similar to this:
public abstract class Item has 5 Nested classes which all are abstract and static that represent the types of Items. Every Nested class has an unique use(), delete() (Which finalizes the class instance) and sell()(triggers delete) void. They also have optional getter and setter methods, like the setAll() method which fills all necesary fields.
Default: Has base price, tradeability boolean, String name, etc... Very flexible
Weapon: Else than the things that the Default type has, it has integers for stat bonus on being equipped(used in the equip() and unequip() voids). Interacts with public class Hero.
Equipment: Similar to Weapon, just that it has an Enum field called 'EquipSlot' that determines where it is equipped.
Consumable: Similar to default, just that has a consume() void that enables the player to apply certain effects to an Hero when using it. Consuming usually means triggering the delete() void.
Special: Usually quest related items where the 'Tradeable' boolean is static, final and always false.
Now, the way that I make customized items is this.
First, I make a new class (Not abstract)
Then, I make it extend Item.ItemType
Then, I make a constructor which has the setAll(info) void inside.
Then, I can use this class in other classes.
It all looks like this:
package com.ep1ccraft.Classes.Items.Defaults;
import com.ep1ccraft.apis.Item.*;
public class ItemExample extends Item.Default {
public ItemExample() { // Constructor
this.setAll(lots of arguments here);
}
}
then I can do:
ItemExample something = new ItemExample();
And I have a perfect ItemExample with all the properties that I want, So, I can make various instances of it, and use amazing methods like 'getName()' and that kind of stuff.
The problems come to Naming the instances, as I do not know how to make an automated form that will give the instance a Different name from the other instance so they don't collide. Also, I want to implement an inventory system that uses slots as containers and can keep stacks (Stackable items only), also the main feature of it is that you can drag and drop them into other slots (For example, to reorganize or to move to another inventory instance like a bank, or to place in an hero's weapon or equipment slots, if it is allowed) and that you can click on them to display a screen that shows the name, description and possible actions of the Item (Which trigger the previously mentioned delete() and use() voids).
Thank you for reading all that! I know that maybe I'm asking for too much, but I'll appreciate any answers anyway!
So basically, you're asking for a unique identifier for your object. There are probably countless approaches to this, but the three different approaches that immediately come to mind are:
1: A UUID; something like:
java.util.UUID.randomUUID()
Pros: A very, very simple solution...
Cons: It does generate a large amount of bytes (16 + the object itself), taking memory / disk storage, which might be an issue in a MMO
2: A global running number; something like:
class ID {
private static volatile long id = 0;
public synchronized long nextId() {
return id++;
}
}
Pros: Again, a simple solution...
Cons: Even though this is a very simple class, it does contain "volatile" and "synchronized", which might be an issue for an MMO, especially if it is used heavily. Also, What happens after X years of running time, if you run out of numbers. A 64 bit long does require quite a lot of named objects to be created, it may not be an issue after all... you'll have to do the math yourself.
3: Named global running numbers; something like:
class NamedID {
private static volatile Map<String, Long> idMap = new HashMap<String, Long>();
public synchronized long nextId(String name) {
Long id = idMap.get(name);
if (id == null) {
id = 0;
} else {
id++;
}
idMap.put(name, id);
return id;
}
}
Pros: You get id's "localized" to whatever name you're using for it.
Cons: A bit more complex solution, and worse than "2" in terms of speed, since the synchronzation lasts longer.
Note: I couldn't figure out how to make this last suggestion faster; i thought of using a ConcurrentHashMap, but that won't work since it works on a lower level; i.e. it will not guarantee that two thread does not interfere with each other between the idMap.get and the idMap.put statements.
I have some code that consists of a lot (several hundreds of LOC) of uggly conditionals i.e.
SomeClass someClass = null;
if("foo".equals(fooBar)) {
// do something possibly involving more if-else statments
// and possibly modify the someClass variable among others...
} else if("bar".equals(fooBar)) {
// Same as above but with some slight variations
} else if("baz".equals(fooBar)) {
// and yet again as above
}
//... lots of more else ifs
} else {
// and if nothing matches it is probably an error...
// so there is some error handling here
}
// Some code that acts on someClass
GenerateOutput(someClass);
Now I had the idea of refactoring this kind of code something along the lines of:
abstract class CheckPerform<S,T,Q> {
private CheckPerform<T> next;
CheckPerform(CheckPerform<T> next) {
this.next = next;
}
protected abstract T perform(S arg);
protected abstract boolean check(Q toCheck);
public T checkPerform(S arg, Q toCheck) {
if(check(toCheck)) {
return perform(arg);
}
// Check if this CheckPerform is the last in the chain...
return next == null ? null : next.checkPerform();
}
}
And for each if statment generate a subclass of CheckPerform e.g.
class CheckPerformFoo extends CheckPerform<SomeInput, SomeClass, String> {
CheckPerformFoo(CheckPerform<SomeInput, SomeClass, String> next) {
super(next);
}
protected boolean check(String toCheck) {
// same check as in the if-statment with "foo" above"
returs "foo".equals(toCheck);
}
protected SomeClass perform(SomeInput arg) {
// Perform same actions (as in the "foo" if-statment)
// and return a SomeClass instance (that is in the
// same state as in the "foo" if-statment)
}
}
I could then inject the diffrent CheckPerforms into eachother so that the same order of checks are made and the corresponding actions taken. And in the original class I would only need to inject one CheckPerform object. Is this a valid approach to this type of problem? The number of classes in my project is likely to explode, but atleast I will get more modular and testable code. Should I do this some other way?
Since these if-else-if-...-else-if-else statments are what I would call a recurring theme of the code base I would like to do this refactoring as automagically as possible. So what tools could I use to automate this?
a) Some customizable refactoring feature hidden somewhere in an IDE that I have missed (either in Eclipse or IDEA preferably)
b) Some external tool that can parse Java code and give me fine grained control of transformations
c) Should I hack it myself using Scala?
d) Should I manually go over each class and do the refactoring using the features I am familiar with in my IDE?
Ideally the output of the refactoring should also include some basic test code template that I can run (preferably also test cases for the original code that can be run on both new and old as a kind of regression test... but that I leave for later).
Thanks for any input and suggestions!
What you have described is the Chain of Responsibility Pattern and this sounds like it could be a good choice for your refactor. There could be some downsides to this.
Readability Because you are going to be injecting the the order of the CheckPerformers using spring or some such, this means that it is difficult to see what the code will actually do at first clance.
Maintainence If someone after you wants to add a new condition, as well as adding a whole new class they also have to edit some spring config. Choosing the correct place to add there new CheckPerformer could be difficult and error prone.
Many Classes Depending on how many conditions you have and how much repeated code within those conditions you could end up with a lot of new classes. Even though the long list of if else its very pretty, the logic it in one place, which again aids readability.
To answer the more general part of your question, I don't know of any tools for automatic refactoring beyond basic IDE support, but if you want to know what to look for to refactor have a look at the Refactoring catalog. The specific of your question are covered by replace conditional with Polymorphism and replace conditional with Visitor.
To me the easiest approach would involve a Map<String, Action>, i.e. mapping various strings to specific actions to perform. This way the lookup would be simpler and more performant than the manual comparison in your CheckPerform* classes, getting rid of much duplicated code.
The actions can be implemented similar to your design, as subclasses of a common interface, but it may be easier and more compact to use an enum with overridden method(s). You may see an example of this in an earlier answer of mine.
Unfortunately I don't know of any automatic refactoring which could help you much in this. Earlier when I did somewhat similar refactorings, I wrote unit tests and did the refactoring step-by-step, manually, using automated support at the level of Move Method et al. Of course since the unit tests were pretty similar to each other in their structure, I could reuse part of the code there.
Update
#Sebastien pointed out in his comment, that I missed the possible sub-ifs within the bigger if blocks. One can indeed use a hierarchy of maps to resolve this. However, if the hierarchy starts to be really complex with a lot of duplicated functionality, a further improvement might be to implement a DSL, to move the whole mapping out of code into a config file or DB. In its simplest form it might look something like
foo -> com.foo.bar.SomeClass.someMethod
biz -> com.foo.bar.SomeOtherClass.someOtherMethod
baz -> com.foo.bar.YetAnotherClass.someMethod
bar -> com.foo.bar.SomeOtherClass.someMethod
biz -> com.foo.bar.DifferentClass.aMethod
baz -> com.foo.bar.AndAnotherClass.anotherMethod
where the indented lines configure the sub-conditions for each bigger case.