Trying to move away from many if-else statements
I have been trying to refactor some code so I could extend certain features
without having to modify my existing code.
I used to have some code like this:
publicclass Dogextends Animal{
}
publicclass Catextends Animal{
}
publicclass MyParser(){
publicvoid parse(){
...
MyBuilder builder =new MyBuilder();
Animal animal;
if (tag.equals("dog"))
animal = builder.buildDog();
elseif (tag.equals("cat"))
animal = builder.buildCat();
else
asertfalse :"Unsupported tag";
}
}
What I wanted is to be able to add support for a new "animal" without having
to change the parse method and add a new if statement.
I tried to do something like this:
publicclass Dogextends Animal{
static{
MyParser.registerBuilder("dog", DogBuilder);
}
}
publicclass Catextends Animal{
static{
MyParser.registerBuilder("cat", CatBuilder);
}
}
publicclass MyParser(){
privatestatic Map builders =new HashMap();
publicstatic registerBuilder(String tag, MyBuilder builder){
builders.put(tag, builder)
}
publicvoid parse(){
...
MyBuilder builder = getBuilder(tag);
if (builder ==null)
asertfalse :"Unsupported tag";
Animal animal = builder.build();
}
}
This way I could add a new "animal" and register a new builder for it in
that class. This would be a nice way to seperate responsibilities I thought.
The only problem with this is that the builder is not registered until the
specific Animal class is referenced. In this particular case it is never,
because no builders are registered yet so I cannot build any animal. It's
like the chicken or the egg problem :)
I can register the builders inside a static initializer of MyParser, but
then I have almost the same thing as a list of if statements again.
There is no way to force a class to load before it is really referenced, right?
Maybe there is some nice Design Pattern for this?
[4091 byte] By [
SmellyCata] at [2007-10-3 2:07:25]

You've got the right idea. Polymorphism is supposed to handle this sort of thing.
Why Builder? Looks like little more than a factory class.
public class AnimalFactory
{
public static Animal getInstance(Class clazz) throws ClassCastException
{
return (Animal)clazz.getInstance();
}
}
You've gotta have that .class file in the CLASSPATH, but once you have it there you can create an instance at will.
I ran this once and created a List of Dogs and Cats. Then I created a new Fish class, compiled the .java source, and re-ran the AnimalFactory with some Fish mixed in. I was able to add a Fish to my AnimalFactory without having register anything.
public interface Animal
{
String getName();
}
/code]
[code]
public abstract class AbstractAnimal implements Animal
{
public static final String DEFAULT_NAME = "ANIMAL";
private String name;
public AbstractAnimal() { this(DEFAULT_NAME); }
public AbstractAnimal(String name) { this.name = name; }
public String getName() { return name; }
}
public class Dog extends AbstractAnimal
{
public static final String DEFAULT_NAME = "Buster";
public Dog() { super(DEFAULT_NAME); }
public Dog(String name) { super(name); }
}
public class Cat extends AbstractAnimal
{
public static final String DEFAULT_NAME = "Fluffy";
public Cat() { super(DEFAULT_NAME); }
public Cat(String name) { super(name); }
}
public class Fish extends AbstractAnimal
{
public static final String DEFAULT_NAME = "Bubbles";
public Fish() { super(DEFAULT_NAME); }
public Fish(String name) { super(name); }
}
import java.util.List;
import java.util.ArrayList;
public class AnimalFactory
{
public static void main(String [] args)
{
try
{
List<Animal> animals = new ArrayList<Animal>(args.length);
for (int i = 0; i < args.length; ++i)
{
Animal a = (Animal)Class.forName(args[i]).newInstance();
animals.add(a);
}
for (Animal a : animals)
{
System.out.println(a.getName());
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
public abstract class AbstractAnimal implements Animal
{
public static final String DEFAULT_NAME = "ANIMAL";
private String name;
public AbstractAnimal() { this(DEFAULT_NAME); }
public AbstractAnimal(String name) { this.name = name; }
public String getName() { return name; }
}
Compiled and ran it like this:
C:\polymorphism>javac -d . *.java
C:\polymorphism>java AnimalFactory Dog Cat Dog Dog Cat Fish Fish
%
I used builder classes, because I need to parse some DOM Element object and pass other parameters such as a Locale to the build method.
I see that the core of your example is "Class.forName".
I could use that to create the correct builder object. Perhaps do something like
(MyBuilder)Class.forName(tag + "Builder").newInstance();
But it does mean that the tag (which I get from an XML element name) must always match with (part of) a classname. Is there some other way to do it without having this restriction?
If you're trying to create Java objects from XML there's a better way. Look into XStream or another XML-Java binding library. Someone else has already done it, and better than you will.%
> If you're trying to create Java objects from XML
> there's a better way. Look into XStream or another
> XML-Java binding library. Someone else has already
> done it, and better than you will.
Ok.... maybe.... But could you also comment about it in general, because I had similar difficulties and it was not related to XML.
comment on what in general? I'm not sure that I have a cookbook solution for all occasions.%
> comment on what in general? I'm not sure that I have
> a cookbook solution for all occasions.
How can you create an object when you only have a string. You know which object to create by looking at the string, but the string itself does not have enough information to use a Class.forName method.
So you want to register something like string -> class or instance, but without a list of if statements.
Currently I am thinking about using reflection and looking for all *Builder classes in the package and do a Class.forName for them. That would trigger a static initializer in those classes which allow the builders to register themselves.
> How can you create an object when you only have a
> string. You know which object to create by looking at
> the string, but the string itself does not have
> enough information to use a Class.forName method.
> So you want to register something like string ->
> class or instance, but without a list of if
> statements.
Configuration source - like a file or a database. It provides a mapping from the string to whatever you want.
> This way I could add a new "animal" and register a
> new builder for it in
> that class. This would be a nice way to seperate
> responsibilities I thought.
> The only problem with this is that the builder is not
> registered until the
> specific Animal class is referenced. In this
> particular case it is never,
> because no builders are registered yet so I cannot
> build any animal. It's
> like the chicken or the egg problem :)
>
> I can register the builders inside a static
> initializer of MyParser, but
> then I have almost the same thing as a list of if
> statements again.
>
> There is no way to force a class to load before it is
> really referenced, right?
> Maybe there is some nice Design Pattern for this?
The class loads as soon as a file that uses the 'class' is loaded. You can reference the class in the parser to force the load. But that would mean you still have to modify the parser on each load.
You will have to define a system to do this for you. You either modify the source code by adding this new text somewhere, or you modify an external resource.
I would rather see you modify the source code alone (by adding the if or otherwise) as opposed to the source code AND an external resource for a problem this small.
> The class loads as soon as a file that uses the
> 'class' is loaded. You can reference the class in
> the parser to force the load. But that would mean
> you still have to modify the parser on each load.
>
> You will have to define a system to do this for you.
> You either modify the source code by adding this new
> text somewhere, or you modify an external resource.
>
> I would rather see you modify the source code alone
> (by adding the if or otherwise) as opposed to the
> source code AND an external resource for a problem
> this small.
If the source needs to be modified, then it means that the functionality cannot be extended by modifying my source. That is what I want to prevent. It sounds like you say that this is not possible.
> >
> > There is no way to force a class to load before it is
> > really referenced, right?
> > Maybe there is some nice Design Pattern for this?
>
> The class loads as soon as a file that uses the
> 'class' is loaded.
Not exactly. Or at least not in the Sun VM (and due to the spec it is unlikely others do it differently.)
It is loaded when it is used. In practice this means that classes referenced inside another class are loaded.
1. When the method that uses them is first accessed (or even when the code line runs)
2. Exceptions that are thrown from a method, declared as thrown or not, which are allowed to escape the method.
3. Parent classes when the class itself loads.
4. Return types probably when the class loads but that is one case that I have not investigated.
>
> What I wanted is to be able to add support for a new
> "animal" without having
> to change the parse method and add a new if
> statement.
>
Use a configuration source as I already suggested.
Provide a factory (builder) for each type. Each type implements an interface that your calling code knows.
Your program then does the following.
1. Load the configuration file. Create a map from a name to a factory.
2. For each tag look it up in the map.
3. Use the factory name (full class name) to load the factory and create an instance. You can cache the instance or not as you feel like.
4. Call the factory instance to create a instance of the specific type.
5. You code ONLY uses the interface that the type implements.
>
> Use a configuration source as I already suggested.
Okay. I am using the XML Schema itself now to declare the factory/builder classes with fixed attributes. Since it is an XML Schema other people
can extend it too and add more supported tags.
I think this is a nice compromise and am satisfied with it for now :)
Has there ever been a discussion about having the ability to let classes declare that they must be loaded immediately? I think that would be a nice feature that helps implement some design patterns.
> > Has there ever been a discussion about having the> ability to let classes declare that they must be> loaded immediately? I think that would be a nice> feature that helps implement some design patterns.What do you mean by
> >
> > Has there ever been a discussion about having the
> > ability to let classes declare that they must be
> > loaded immediately? I think that would be a nice
> > feature that helps implement some design patterns.
>
> What do you mean by 'immediately'?
For example after the Virtual Machine has started, but before your main function is called.
Maybe it will be possible with the pluggable annotation processing API in Java 1.6.
I believe you should think carefully about what you are intending to do. Can you list what benefit you expect to gain by not having to modify the source code?
Normally what you are proposing is not done on a class level but is done on more of a package or "bundle" level by such systems as OSGI or the Java Extension Mechanism. You are essentially proposing a plugin system.
> I believe you should think carefully about what you
> are intending to do. Can you list what benefit you
> expect to gain by not having to modify the source
> code?
I'm a little surprised by this question, because I thought
writing extendible code without it having to be modified is
an important concept in design patterns.
The benefits? Easier maintenance, because you can
add new features without having to worry about breaking
your existing code.
And someone else can use your code as a 3rd party library
and add features even when they don't have your source
code, or want to compile it again.
> Normally what you are proposing is not done on a
> class level but is done on more of a package or
> "bundle" level by such systems as OSGI or the Java
> Extension Mechanism. You are essentially proposing a
> plugin system.
I will take a look at OSGI and the Java Extension Mechanism.