可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have a Gene
class that keeps track of genes. Gene
has a method for calculating the distance between two genes. Are there any reasons to make it static?
Which is better?
public static int geneDistance(Gene g0, Gene g1)
or
public int geneDistance(Gene other)
Arguments for/against making it static? I understand what it means for a member to be static, I'm just interested in its implications for maximum cleanliness/efficiency/etc.
I repeat the same pattern for returning trimmed versions of two genes, finding matches between genes, finding matches between animals (which contain collections of genes), etc.
回答1:
Instance, not static
For this case I think the second choice is clearly better. If you think about it, any method could be implemented as static if you are willing to pass the object to it, this only seems to be a special case because the other parameter is also an instance.
Therefore, our search for symmetry and abstraction is slightly offended by having to choose between the two instance objects for the dot operator. But if you look at .method
as .
then operator, it isn't really a problem.
Plus, the only way to do functional-style chaining is with an attribute, i.e., instance method. You probably want thing.up.down.parent.next.distance(x)
to work.
回答2:
When you make a method static, it means that the method can be called without an instance of the class. It also means that the method cannot access instance variables unless it is passed a reference to an object.
Sometimes, it makes sense to make a method static, because the method is associated with the class, but not a particular instance of the class. For example, all the parseX methods, such as Integer.parseInt(String s
). This converts a String
to an int
, but does not have anything to do with a particular instance of an Integer
object.
If, on the other hand, a method must return some data which is unique to a particular instance of an object, (like most getter and setter methods), then it can't be static.
回答3:
IMO there is no absolute "better", but public int geneDistance(Gene other)
is stylistically more similar to other methods in Java (e.g. Object.equals, Comparable.compareTo), so I'd go that way.
回答4:
I prefer the second form, i.e. instance method for the following reasons:
- static methods make testing hard because they can't be replaced,
- static methods are more procedural oriented (and thus less object oriented).
IMO, static methods are ok for utility classes (like StringUtils) but I prefer to not abuse using them.
回答5:
My rewording of Charle's answer :
If the method in question intends to use the state of the underlying object in any way, make it an instance method. Else, make it static.
Which depends on the way the object's class is designed.
In your case, alphazero, probably the int geneDistance(Gene g0, Gene g1) does not really depend on the state of the Gene instance it is called on. I would make this method static. And put it in a utility class like GeneUtils.
Of course, there might be other aspects of your problem that I am not aware of, but this is the general thumb of rule that I use.
P.S. -> The reason I would not put the method in the Gene class itself is because a Gene should not be responsible for computing it's distance from another Gene. ;-)
回答6:
public static int geneDistance(Gene g0, Gene g1)
would be part of a separate utility class like Collections
and Arrays
in Java whereas public int geneDistance(Gene other)
will be part of the Gene
class. Considering you have other operations like "trimmed versions of two genes, finding matches between genes, finding matches between animals (which contain collections of genes), etc" I would create a separate static utility class for them as these operations aren't semantically meaningful to what a Gene
is.
If the the semantics of "gene distance" can be wrapped up into your equals(Object o)
method then you could consume it there or else include it in your static utility.
回答7:
I would like to start answering on your question with the new one: What your class Gene is responsible for? May be you have heard about the 'Single-Responsibility Principle': A class should have only one reason to change. So, I believe if you answer this question you will be able to decide how your application should be designed. In this particular case, I would not use neither the first approach nor the second one. In my opinion it is much better to define new responsibility and encapsulate it in a separate class or may be a function.
回答8:
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.
Open For Extension, Closed for Modification
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
Tell Don't Ask
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));
}
}
回答9:
Here's a meta-answer, and a fun exercise: survey a bunch of the Java SDK's library classes and see if you can categorize the commonalities between static methods in different classes.
回答10:
In this particular case, I will make it an intance method. BUT if you have a logical answer when g0 is null then use BOTH (this happen more often than you think).
For example, aString.startsWith()
, if the aString is null, you may think it is LOGICAL to return null (in case you think the function can be NULL-TOLERATE). This allows me to simplify my program a bit as there is no need to have aString check null in the client code.
final Stirng aPrefix = "-";
final Vector aStrings = new Vector();
for(final String aString : aStrings) {
if (MyString.startsWith(aString, aPrefix))
aStrings.aStringadd();
}
instead of
final Stirng aPrefix = "-";
final Vector aStrings = new Vector();
for(final String aString : aStrings) {
if ((aString != null) && aString.startsWith(aPrefix))
aStrings.aStringadd();
}
NOTE: This is an overly simplified example.
Just a thought.
回答11:
I would make this an instance method. But that might be due to the fact that I have no clue of genes ;)
Instance methods can be overridden by subclasses which greatly reduces the complexity of your code (less need for if-statements). In the static method example, what will happen I you get a specific type of gene for which the distance is calculated differently? Ad another static method? If you'd have to process a polymorphic list of genes you'd have to look a the type of gene to select the correct distance method... which increases coupling and complexity.
回答12:
I'd select the second approach. I see no advantage in making the method static. Since the method is in the Gene class, making it static only adds one extra parameter with no extra gain. If you need a util class, that's a whole different deal. But in my opinion there's usually no need for a util class if you can add the method to the class in question.
回答13:
I think the problem domain should inform the answer beyond the general stylistic and/or OO considerations.
For example, I'm guessing that for the domain of genetic analysis, the notions of 'gene' and 'distance' are fairly concrete and will not require specialization through inheritance. Were that not the case, one could make a strong case for opting for the instance methods.
回答14:
The main reason to prefer the instance method is polymorphism. A static method cannot be overridden by a subclass, which means you can't customize the implementation based on the instance type. This might not apply in your case, but it is worth mentioning.
If gene distance is completely independent of the type of the gene, I would prefer using a separate utility class to make that independence more explicit. Having a geneDistance
method as part of the Gene
class implies that distance is a behavior related to the gene instance.
回答15:
My answer is very opinionated.
I would go the same way as one of the StringUtils.getLevenshteinDistance
implementation in StringUtils.
public interface GeneDistance{
public int get();
}
public class GeneDistanceImpl implements GeneDistance{
public int get(){ ... }
}
public class GeneUtils{
public static int geneDistance(Gene g0, Gene g1){
return new GeneDistanceImpl(g0, g1).get();
}
}
Some points for doing it this way
- There might be several distance implementations, so an utility method is more preferable than
g0.distanceTo(g1)
- I can static-import it for a short notation
- I can test my implementation
I can also add this:
class Gene{
// ... Gene implementation ...
public int distanceTo(Gene other){
return distance.get(this, GeneUtils.getDefaultDistanceImpl());
}
public int distanceTo(Gene other, GeneDistance distance){
return distance.get(this, other);
}
}
One of the reasons to make a complex method completely static is the performance. static
keyword is a hint for a JIT compiler that the method can be inlined. In my opinion you s/he don't need to bother about such things unless their method calls are almost instantaneous - less than a microsecond, i.e. a few string operations or a simple calculation. This might be the reason why Levenshtein distance was made completely static in the latest implementation.
回答16:
Two important considerations which have not been mentioned are whether gene1.geneDistance(gene2) is always expected to match gene2.geneDistance(gene1), and whether Gene is and always will be a sealed class. Instance methods are polymorphic with respect to the types of the things upon which they are invoked, but not the types of their arguments. This can cause some confusion if the distance function is supposed to be transitive, but things of different types might compute distance differently. If the distance function is supposed to be transitive, and is defined as being the shortest transformation that either class knows about, a good pattern may be to have a protected instance method int getOneWayDistance(Gene other)
and then have something like:
public static int geneDistance(Gene g0, Gene g1)
{
int d0=g0.getOneWayDistance(g1);
int d1=g1.getOneWayDistance(g0);
if (d0 < d1) return d0; else return d1;
}
Such a design will ensure that distance relation behaves transitively, while allowing individual types to report shortcuts to instances of other types that those other types may not know about.