Подтвердить что ты не робот

Java: когда создавать методы static v. Instance

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

Что лучше?

public static int geneDistance(Gene g0, Gene g1)

или

public int geneDistance(Gene other)

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

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

4b9b3361

Ответ 1

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


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

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

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

Ответ 2

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

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

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

Ответ 3

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

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

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

Ответ 4

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

Ответ 5

Моя формулировка ответа Шарле:

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

Что зависит от способа создания класса объекта.

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

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

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

Ответ 6

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

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

Ответ 7

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

Ответ 8

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

Лично я не думаю, что есть ответ "лучше". Допустимые причины существуют, почему вы не используете класс утилиты, заполненный статическими методами.

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

Полиморфизм

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

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

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

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

Сообщить не спрашивать

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

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

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

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

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

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

Ответ 10

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

Например, aString.startsWith(), если aString имеет значение NULL, вы можете подумать, что LOGICAL возвращает null (если вы считаете, что функция может быть NULL-TOLERATE). Это позволяет мне немного упростить мою программу, так как нет необходимости иметь aString check null в коде клиента.


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

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

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

Ответ 11

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

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

Ответ 12

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

Ответ 13

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

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

Ответ 14

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

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

Ответ 15

Мой ответ очень упрямый.

Я бы пошел так же, как одна из реализаций 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, что метод может быть встроен. По-моему, вам не нужно беспокоиться о таких вещах, если их вызовы методов почти мгновенно - меньше, чем микросекунды, т.е. Несколько строковых операций или простой расчет. Это может быть причиной того, что расстояние Левенштейна было полностью статичным в последней реализации.

Ответ 16

Два важных соображения, которые не были упомянуты, - это то, что всегда следует ожидать, что ген1.geneDistance(ген2) соответствует признаку гена2.geneDistance(ген1) и является ли ген и всегда будет герметичным классом. Методы экземпляров являются полиморфными по отношению к типам вещей, на которые они вызывают, но не по типам их аргументов. Это может вызвать некоторую путаницу, если функция расстояния должна быть транзитивной, но вещи разных типов могут вычислять расстояние по-разному. Если функция расстояния должна быть транзитивной и определяется как кратчайшее преобразование, о котором знает любой класс, хорошим шаблоном может быть метод защищенного экземпляра 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;
}

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