Problem
My problem is that you have a lot of if
conditions to be checked and inside that if
condition you have multiple lines of code. I want to eliminate it and use inheritance instead. For every if
statement I will be using a separate class to handle it. This is more like a derivative of the command pattern but not command pattern itself.
I have an interface which contains only one method which defines the task I want it to be implemented. I have an abstract class implementing that interface and contains a static map and static methods to add values and retrieves the task. The classes I specified will be extending this Handler
class.
This is my scenario:
I am sending a string as parameter to get a specific translator. Let’s say Sinhala or Tamil or English. On each implementation of those translators, I will be adding the necessary values to the map in the Handler
class in a static
block. Basically adding them in the class load time.
Command Interface
public interface Command {
LanguageTranslator getLangTranslator();
}
Handler Abstract class
public abstract class Handler implements Command {
private static Map<String, Command> map = new HashMap<>();
public static void AddToMap(String key, Command command){
map.put(key,command);
}
public static LanguageTranslator getTranslator(String value) throws Exception {
if (!(map.containsKey(value))){
throw new LanguageNotFoundException("Translator not found");
}else {
return map.get(value).getLangTranslator();
}
}
}
Sinhala Translator
public class SiCommand extends Handler {
static {
Handler.AddToMap("1",new SiCommand());
}
@Override
public LanguageTranslator getLangTranslator() {
return new SiTranslator();
}
}
Tamil Translator
public class TiCommand extends Handler {
static {
Handler.AddToMap("2",new TiCommand());
}
@Override
public LanguageTranslator getLangTranslator() {
return new TiTranslator();
}
}
English Translator
public class EnCommand extends Handler {
static {
Handler.AddToMap("3",new EnCommand());
}
@Override
public LanguageTranslator getLangTranslator() {
return new EnTranslator();
}
}
Demo
RowTranslator rowTranslator = Handler.getTranslator("2");
Questions
Since all values are assigned to the map during the class loading time these are my questions:
-
Is using
static
blocks like this a good practice? -
I want to eliminate the use of
if
conditions as I mentioned before. By using this method, whenever someone wants to add a newif
condition, you just only have to create a new class extending handler and implement it. Therefore the outside user doesn’t need to know the internal handling of theCommand
‘s subclasses. The output is just gonna be a translator. Is this a good way of doing it? What are the better alternatives? What should be adjusted?
Note: I will also be focusing strictly on Open/Close principle as well.
Solution
Static registration should be avoided. Not only because it is global code, burdening start-up and the class must be loaded (!), but also for unit-testing and diversification (proposing Tamil+English and Sinhala+Tamil as two configurable choices).
This is almost something for declarative extensions, say in XML with class loading with Class.forName
.
You also might opt for loading by convention or configuration: instead of 1, 2, 3 use “si”, “ti”, “en” checked by a config properties “translators=si,ti,en” or “si=mypackage.SiTranslator”.
Or a discovery system: java SPI (Service Provider Interface): in META-INF/services/ a text file mypackage.Command
containing the class name(s). You can have several jars.
A map held in a singleton object is not bad, with a small, not very dynamic domain, like languages.
Map<String, Command> commandsMap = new HashMap<>();
But you might consider the overhead of 3 or more languages, when only 2 are used on average. Say if they load a dictionary. In that case: register first
producers of those classes, which you then place in the map with a java 8 Supplier<Command>
:
register("si", SiTranslator::new);
register("ta", TaTranslator::new);
register("en", EnTransleightour::new);
A special factory class as your Handlers does not seem needed, or can in java 8 be defined as interface annotated with @FunctionalInterface
and written as anonymous lambda.
Open/Close would imply using the java SPI, where you plug-in a new language, possibly in a separate jar. You still better use “si” (best the Locale key) instead of a number “1”. One interface method would be String getKey()
.
However I would not say that a declarative or map registry at one place would be a sin. A specified orchestrated system behavior (at one point) is just as worthwile as independent extendibility. Only if you want to plug-in new languages, say separate jars, then it matters.
The Open/Close Principle is important for multitudes, maybe fine grained business rules. Open domains of a large magnitude.
-
Using static blocks to do wiring like this is generally not a good idea, for multiple reasons:
- It makes unit testing harder
- It gives the class another responsibility, or reason to change
- It essentially turns the map in
Handler
into a global variable, and that is bad for a number of reasons: http://c2.com/cgi/wiki?GlobalVariablesAreBad
-
The general idea of your design is OK, but it can be simplified, and the names can be improved upon.
- I would call your
Handler
aTranslatorRegistry
, since its responsibility is to hold and provideTranslator
s. - The individual language
Command
s should be called “factories”, as per the Factory pattern. (http://www.oodesign.com/factory-pattern.html).
However, these factories don’t seem to serve a purpose. TheHandler
could simply hold references to theLanguageTranslator
s directly. - I’m not sure why your individual language
Command
s inherit fromHandler
, but in the code you’ve posted it has no purpose. It can be removed without affecting the behavior of the program.
- I would call your
Using these suggestions, your code would look something like this:
class TranslatorRegistry {
private Map<String, LanguageTranslator> translators = new HashMap<>();
public void register(String key, LanguageTranslator translator) {
translators.put(key, translator);
}
public LanguageTranslator get(String key) {
if (!translators.containsKey(value)) {
throw new LanguageNotFoundException("Translator not found");
} else {
return translators.get(key);
}
}
}
and then:
// Somewhere in your program:
void registerTranslators() {
translatorRegistry.register("1", new SiTranslator());
translatorRegistry.register("2", new TiTranslator());
translatorRegistry.register("3", new EnTranslator());
}
The method that you have followed is one of the recommended ways of refactoring i.e Replace Conditional statements with Polymorphism. In one my recent projects, I had applied this sort of refactoring. One of the changes that I had done was to create a Map <Enum,Command>
. I suppose that the code can be more readable by applying Handler.AddToMap(Languages.ENGLISH, new EnCommand());
Secondly, one of the problems can be that when a new class is implemented by somebody, if the static block is not added, then the translator may return null. My advice would be to follow a few norms.
- You need to specify either a
DefaultHandler
that will execute if the command Object is not found. So, setting a default Language Handler option is crucial. - Perhaps you may want to exit or throw an appropriate exception if the Language is not found.
- What I am about to suggest may not be the best practice but I am still gonna go for it. You may also want to initialize the hashMap in one place on startup and have a proper javadoc indicating for the next generation of developers that an entry has to be added to the map in order to enable that language handler. Although this does break some design principles, I find that when I look at my initialize method, I can quickly see all the language handlers that are supported. I find this readability to be more convenient. I am sure this may not resonate well in all scenarios, but this approach has worked for me.
Currently you have
public class TiCommand extends Handler {
static {
Handler.AddToMap("2",new TiCommand());
}
which calls
public static void AddToMap(String key, Command command){
map.put(key,command);
}
Note that this would usually be called addToMap
in Java.
Now let’s add FrCommand
public class FrCommand extends Handler {
static {
Handler.AddToMap("2", new FrCommand());
}
You of course point out that I can’t add FrCommand
as 2. You already used 2. But here’s the thing. What’s stopping me from using 2? Absolutely nothing except me realizing that I shouldn’t. And if I do reuse an existing number, what happens? In this case, either FrCommand
would overwrite TiCommand
or vice versa.
There are several potential fixes here.
- You could have
addToMap
check that there are no conflicts before adding. - You could change the
Map
to aList
instead and use numeric keys rather than strings. - You could switch the string keys to be more unique or obvious. For example, instead of numbers, you could have “en”, “ti”, and “si”.
- Make the key the class name, e.g.
TiCommand
.
I’d lean towards doing both #1 and #3. #3 seems a clearer system, and #1 will catch it if someone makes a duplicate.