Java: когда делать методы статическими по сравнению с экземпляром

У меня есть класс Gene, который отслеживает гены. Gene есть метод расчета расстояния между двумя генами. Есть ли причины сделать его статичным?

Что лучше?

public static int geneDistance(Gene g0, Gene g1)

or

public int geneDistance(Gene other)

Аргументы за/против статичности? Я понимаю, что значит для члена быть статичным, меня просто интересуют его последствия для максимальной чистоты/эффективности/и т.д.

Я повторяю тот же шаблон для возврата урезанных версий двух генов, поиска совпадений между генами, поиска совпадений между животными (которые содержат наборы генов) и т. д.


person Community    schedule 24.10.2009    source источник
comment
Какой интересный вопрос. У меня есть собственное мнение, но я не чувствую, что оно может квалифицироваться как что-то большее, чем вкус, поэтому я не буду отвечать.   -  person Jonathan Feinberg    schedule 25.10.2009
comment
Это должна быть вики сообщества.   -  person IAdapter    schedule 25.10.2009
comment
рассмотрите вместо этого имя DistanceTo(Gene that). У вас есть неявный контекст, который должен быть отражен в именовании.   -  person Thorbjørn Ravn Andersen    schedule 25.10.2009
comment
...или еще лучше getDistanceTo(Gene g).   -  person Johnny Baloney    schedule 24.10.2013


Ответы (16)


Экземпляр, не статичный


В этом случае я думаю, что второй вариант явно лучше. Если подумать, любой метод может быть реализован как статический, если вы хотите передать ему объект, это только кажется частным случаем, потому что другой параметр также является экземпляром.

Таким образом, наш поиск симметрии и абстракции слегка нарушен необходимостью выбирать между двумя объектами-экземплярами для оператора точки. Но если вы посмотрите на .method как на ., а затем на оператор, это не проблема.

Кроме того, единственный способ сделать цепочку в функциональном стиле — это использовать атрибут, то есть метод экземпляра. Вероятно, вы хотите, чтобы thing.up.down.parent.next.distance(x) работало.

person DigitalRoss    schedule 24.10.2009

Когда вы делаете метод статическим, это означает, что метод может быть вызван без экземпляра класса. Это также означает, что метод не может получить доступ к переменным экземпляра, если ему не передана ссылка на объект.

Иногда имеет смысл сделать метод статическим, потому что метод связан с классом, а не с конкретным экземпляром класса. Например, все методы parseX, такие как Integer.parseInt(String s). Это преобразует String в int, но не имеет ничего общего с конкретным экземпляром объекта Integer.

Если, с другой стороны, метод должен возвращать некоторые данные, которые уникальны для конкретного экземпляра объекта (как и большинство методов получения и установки), то он не может быть статическим.

person Charles Salvia    schedule 24.10.2009
comment
Это также означает, что сам метод может обращаться только к переменным класса, которые также являются статическими. Хм? Статический метод для данного экземпляра a может получить доступ ко всем полям и методам a. - person Jonathan Feinberg; 25.10.2009
comment
Он имеет в виду, что они не могут получить доступ к this, поскольку для его вызова не требуется экземпляр. - person mk12; 25.10.2009
comment
Правильно - я должен был сформулировать это более похоже на: статический метод не может получить доступ к переменным экземпляра, ЕСЛИ ему не передается ссылка на объект - person Charles Salvia; 25.10.2009
comment
Я думаю, что @Charles говорит без ссылки на public static int geneDistance(Gene g0, Gene g1) ОП. Очевидно, он имеет в виду статические методы внутри класса, действующие на класс, а не на переданные экземпляры. - person non sequitor; 25.10.2009
comment
Чарльз, вы можете отредактировать свой ответ, чтобы уточнить. - person Jonathan Feinberg; 25.10.2009

ИМО нет абсолютного «лучше», но public int geneDistance(Gene other) стилистически больше похож на другие методы в Java (например, Object.equals, Comparable.compareTo), поэтому я бы пошел по этому пути.

person Erich Kitzmueller    schedule 24.10.2009

Я предпочитаю вторую форму, то есть метод экземпляра, по следующим причинам:

  1. статические методы усложняют тестирование, потому что их нельзя заменить,
  2. статические методы более процедурно-ориентированы (и, следовательно, менее объектно-ориентированы).

IMO, статические методы подходят для служебных классов (таких как StringUtils), но я предпочитаю не злоупотреблять их использованием.

person Community    schedule 24.10.2009
comment
+1 Обратите внимание, что статический импорт может сделать служебные классы очень чистыми, что является редким случаем для статики. например Что-то вроде структуры утверждений для проектирования по контракту. - person Michael Easter; 11.11.2009
comment
Это правда, например. JUnit 4, и мне это нравится. А вот +1 не увидел :) - person Pascal Thivent; 11.11.2009

Моя переформулировка ответа Чарльза:

Если рассматриваемый метод каким-либо образом намеревается использовать состояние нижележащего объекта, сделайте его методом экземпляра. В противном случае сделайте его статическим.

Это зависит от того, как спроектирован класс объекта.

В вашем случае, alphazero, вероятно, int geneDistance(Gene g0, Gene g1) на самом деле не зависит от состояния экземпляра Gene. вызывается. Я бы сделал этот метод статическим. И поместите его в служебный класс, такой как GeneUtils.

Конечно, могут быть и другие аспекты вашей проблемы, о которых я не знаю, но это общее правило, которое я использую.

P.S. -> Причина, по которой я не стал бы помещать этот метод в сам класс Gene, заключается в том, что Gene не должен нести ответственность за вычисление своего расстояния от другого Gene. ;-)

person Community    schedule 11.11.2009
comment
@adwiv Ну, метод получения никогда не может быть статическим, потому что всегда должен быть экземпляр для получения свойства. Но да, я понимаю вашу точку зрения. Отредактировал мой ответ соответственно. - person divesh premdeep; 21.06.2013
comment
+1 и удаление моего комментария, так как теперь он выглядит неуместно после редактирования :) - person Adwiv; 21.06.2013

public static int geneDistance(Gene g0, Gene g1) будет частью отдельного служебного класса, такого как Collections и Arrays в Java, тогда как public int geneDistance(Gene other) будет частью класса Gene. Учитывая, что у вас есть другие операции, такие как «обрезанные версии двух генов, поиск совпадений между генами, поиск совпадений между животными (которые содержат наборы генов) и т. д.», я бы создал для них отдельный статический служебный класс, поскольку эти операции не имеют семантического смысла. что такое Gene.

Если семантика «генного расстояния» может быть включена в ваш метод equals(Object o), вы можете использовать ее там или включить в свою статическую утилиту.

person Community    schedule 24.10.2009
comment
на самом деле изначально у меня была статическая утилита, но потом я переместил все эти функции в Gene и Species. Почему они семантически связаны с генами? - person Nick Heiner; 25.10.2009
comment
Я думаю, что служебный класс Collections существует, поэтому третьим сторонам проще реализовать различные интерфейсы Collection без необходимости реализовывать свои собственные методы min, rotate, sort и т. д. Для класса Gene это не проблема. Рассмотрим класс java.awt.Point, в нем нет служебного класса Points для вычисления расстояния между точками. - person Sam Barnum; 25.10.2009
comment
@Sam Я смотрю не только на проверку одного расстояния, но и на обрезку, сопоставление, сопоставление коллекций и все остальное, что он решает добавить, что соответствует тому, что представляет что-то вроде Collections. @Rosarch, когда я говорю, что это не семантически связано, я имею в виду, что поиск чего-то вроде урезанных версий нескольких генов - это проблема, не обязательно имеющая значение для того, что такое ген (я, конечно, могу ошибаться, поскольку я не биолог, лол :)), но, надеюсь, вы поймете мой дрейф. - person non sequitor; 25.10.2009

Я хотел бы начать отвечать на ваш вопрос с нового: За что отвечает ваш класс Гена? Возможно, вы слышали о «принципе единственной ответственности»: у класса должна быть только одна причина для изменения. Итак, я считаю, что если вы ответите на этот вопрос, вы сможете решить, как должно быть разработано ваше приложение. В данном конкретном случае я бы не стал использовать ни первый подход, ни второй. На мой взгляд, гораздо лучше определить новую ответственность и инкапсулировать ее в отдельный класс или функцию.

person Community    schedule 11.11.2009
comment
+1 Наконец-то кто-то в этой ветке понял истинный смысл ОО! - person MattDavey; 20.09.2013

Я попытаюсь обобщить некоторые из уже приведенных здесь моментов, с которыми я согласен.

Лично я не думаю, что есть ответ «чувствует себя лучше». Веские причины существуют, почему вы не хотите, чтобы служебный класс заполнялся статическими методами.

Короткий ответ: в объектно-ориентированном мире вы должны использовать объекты и все хорошее, что с ними связано (инкапсуляция, полиморфизм).

Полиморфизм

Если метод расчета расстояния между генами варьируется, следует использовать примерно (скорее всего, Strategy) имеют класс Gene для каждого варианта. Инкапсулируйте то, что варьируется. В противном случае вы получите несколько ifs.

Открыто для расширения, закрыто для изменения

Это означает, что если появится новый метод расчета расстояния между генами, вы не должны изменять существующий код, а скорее добавлять новый. Иначе вы рискуете сломать то, что уже есть.

В этом случае вам следует добавить новый класс Gene, а не изменять код, написанный в #geneDistance.

Говорить, не спрашивая

Вы должны указывать своим объектам, что делать, а не спрашивать их об их состоянии и принимать за них решения. Внезапно вы нарушаете принцип единой ответственности, поскольку это полиморфизм.

Тестируемость

Статические методы легко тестировать изолированно, но в будущем вы будете использовать этот статический метод в других классах. Когда дело доходит до тестирования этих классов на изоляции, вам будет трудно это сделать. Или, скорее, нет.

Я позволю Миско есть его высказывание, которое, скорее всего, лучше, чем то, что я могу придумать.

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));
    }    
}
person Community    schedule 11.11.2009

Вот мета-ответ и забавное упражнение: просмотрите группу классов библиотеки Java SDK и посмотрите, сможете ли вы классифицировать общие черты между статическими методами в разных классах.

person Jonathan Feinberg    schedule 24.10.2009
comment
К сожалению, нет мета-проголосов. ;) - person Bombe; 25.10.2009

В данном конкретном случае я сделаю его методом экземпляра. НО, если у вас есть логичный ответ, когда g0 равно нулю, используйте ОБА (это происходит чаще, чем вы думаете).

Например, aString.startsWith(), если aString имеет значение null, вы можете подумать, что ЛОГИЧЕСКИ вернуть значение null (если вы считаете, что функция может быть NULL-TOLERATE). Это позволяет мне немного упростить мою программу, так как нет необходимости проверять нуль aString в клиентском коде.


final Stirng         aPrefix = "-";
final Vector aStrings = new Vector();
for(final String aString : aStrings) {
    if (MyString.startsWith(aString, aPrefix))
        aStrings.aStringadd();
}

вместо


final Stirng         aPrefix = "-";
final Vector aStrings = new Vector();
for(final String aString : aStrings) {
    if ((aString != null) && aString.startsWith(aPrefix))
        aStrings.aStringadd();
}

ПРИМЕЧАНИЕ. Это слишком упрощенный пример.

Просто мысль.

person Community    schedule 24.10.2009

Я бы сделал это методом экземпляра. Но это может быть из-за того, что я понятия не имею о генах;)

Методы экземпляра могут быть переопределены подклассами, что значительно снижает сложность вашего кода (меньше необходимости в операторах if). В примере со статическим методом, что произойдет, если вы получите определенный тип гена, для которого расстояние рассчитывается по-другому? Объявить другой статический метод? Если бы вам пришлось обрабатывать полиморфный список генов, вам пришлось бы искать тип гена, чтобы выбрать правильный метод расстояния... что увеличивает сцепление и сложность.

person Community    schedule 24.10.2009

Я бы выбрал второй подход. Я не вижу преимущества в том, чтобы сделать метод статическим. Так как метод находится в классе Gene, то если сделать его статическим, то будет добавлен только один дополнительный параметр без дополнительного усиления. Если вам нужен класс util, это совсем другое дело. Но, на мой взгляд, обычно нет необходимости в классе util, если вы можете добавить метод в рассматриваемый класс.

person Community    schedule 24.10.2009

Я думаю, что проблемная область должна дать ответ, выходящий за рамки общих стилистических и/или объектно-ориентированных соображений.

Например, я предполагаю, что для области генетического анализа понятия «ген» и «расстояние» достаточно конкретны и не требуют специализации через наследование. Если бы это было не так, можно было бы привести веские доводы в пользу выбора методов экземпляра.

person Community    schedule 24.10.2009

Основной причиной предпочтения метода экземпляра является полиморфизм. Статический метод не может быть переопределен подклассом, что означает, что вы не можете настроить реализацию на основе типа экземпляра. Это может не относиться к вашему случаю, но стоит упомянуть.

Если расстояние между генами полностью не зависит от типа гена, я бы предпочел использовать отдельный служебный класс, чтобы сделать эту независимость более явной. Наличие метода geneDistance как части класса Gene означает, что расстояние — это поведение, связанное с экземпляром гена.

person Community    schedule 25.10.2009

Мой ответ очень субъективен.

Я бы пошел тем же путем, что и одна из реализаций StringUtils.getLevenshteinDistance в 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();
        }
    }

Некоторые моменты для этого

  • Может быть несколько дистанционных реализаций, поэтому служебный метод предпочтительнее, чем g0.distanceTo(g1)
  • Я могу статически импортировать его для краткой записи
  • Я могу проверить свою реализацию
  • Я также могу добавить это:

    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);
        }
    }
    

Одной из причин сделать сложный метод полностью статическим является производительность. Ключевое слово static — это подсказка для JIT-компилятора о том, что метод может быть встроен. На мой взгляд, вам не нужно беспокоиться о таких вещах, если только их вызовы методов не происходят почти мгновенно - менее микросекунды, то есть несколько строковых операций или простой расчет. Это может быть причиной того, что в последней реализации расстояние Левенштейна было сделано полностью статическим.

person Community    schedule 20.09.2013

Два важных момента, которые не были упомянуты, заключаются в том, всегда ли предполагается, что Gene1.geneDistance(gene2) будет соответствовать Gene2.geneDistance(gene1), и будет ли Gene запечатанным классом и всегда будет им. Экземплярные методы полиморфны в отношении типов вещей, для которых они вызываются, но не в отношении типов их аргументов. Это может вызвать некоторую путаницу, если функция расстояния предполагается транзитивной, но объекты разных типов могут вычислять расстояние по-разному. Если предполагается, что функция расстояния является транзитивной и определяется как кратчайшее преобразование, о котором известно любому классу, хорошим шаблоном может быть наличие защищенного метода экземпляра int getOneWayDistance(Gene other), а затем что-то вроде:

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;
}

Такой дизайн гарантирует, что отношение расстояния ведет себя транзитивно, в то же время позволяя отдельным типам сообщать о ярлыках для экземпляров других типов, о которых эти другие типы могут не знать.

person Community    schedule 20.09.2013