I'll try to sum up some of the points already given here to which I agree.
Personally I don't think there is a "feels better" answer. Valid reasons do exist on why you don't wan't a utility class filled with static methods.
The short answer is that in an object oriented world you should use objects and all the good "stuff" that comes with them (encapsulation, polymorphism)
Polymorphism
If the method for calculating the distance between the genes varies, you should roughly (more likely a Strategy) have a Gene class per variation. Encapsulate what varies. Else you will end up with multiple ifs.
That means that if a new method for calculating the distance between genes comes up down the line, you shouldn't modify existing code, but rather add new one. Else you risk breaking what's already there.
In this case you should add a new Gene class, not modify the code written in the #geneDistance
You should tell your objects what to do, not ask them for their state and make decisions for them. Suddenly you break the single responsibility principle since that's polymorphism.
Testability
Static methods may well be easy to test in isolation, but down the road you will make use of this static method in other classes. When it comes to testing that classes on isolation, you will have hard time doing it. Or rather not.
I'll let Misko have his saying which is more likely better than what I can come up with.
import junit.framework.Assert;
import org.junit.Test;
public class GeneTest
{
public static abstract class Gene
{
public abstract int geneDistance(Gene other);
}
public static class GeneUtils
{
public static int geneDistance(Gene g0, Gene g1)
{
if( g0.equals(polymorphicGene) )
return g0.geneDistance(g1);
else if( g0.equals(oneDistanceGene) )
return 1;
else if( g0.equals(dummyGene) )
return -1;
else
return 0;
}
}
private static Gene polymorphicGene = new Gene()
{
@Override
public int geneDistance(Gene other) {
return other.geneDistance(other);
}
};
private static Gene zeroDistanceGene = new Gene()
{
@Override
public int geneDistance(Gene other) {
return 0;
}
};
private static Gene oneDistanceGene = new Gene()
{
@Override
public int geneDistance(Gene other) {
return 1;
}
};
private static Gene hardToTestOnIsolationGene = new Gene()
{
@Override
public int geneDistance(Gene other) {
return GeneUtils.geneDistance(this, other);
}
};
private static Gene dummyGene = new Gene()
{
@Override
public int geneDistance(Gene other) {
return -1;
}
};
@Test
public void testPolymorphism()
{
Assert.assertEquals(0, polymorphicGene.geneDistance(zeroDistanceGene));
Assert.assertEquals(1, polymorphicGene.geneDistance(oneDistanceGene));
Assert.assertEquals(-1, polymorphicGene.geneDistance(dummyGene));
}
@Test
public void testTestability()
{
Assert.assertEquals(0, hardToTestOnIsolationGene.geneDistance(dummyGene));
Assert.assertEquals(-1, polymorphicGene.geneDistance(dummyGene));
}
@Test
public void testOpenForExtensionClosedForModification()
{
Assert.assertEquals(0, GeneUtils.geneDistance(polymorphicGene, zeroDistanceGene));
Assert.assertEquals(1, GeneUtils.geneDistance(oneDistanceGene, null));
Assert.assertEquals(-1, GeneUtils.geneDistance(dummyGene, null));
}
}