Какой стиль лучше (переменная экземпляра против возвращаемого значения) в Java


32

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

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

public class MyClass {
    private int var1;

    MyClass(){
        doSomething();
        doSomethingElse();
        doMoreStuff();
    }

    private void doSomething(){
        var1 = 2;
    }

    private void doSomethingElse(){
        int var2 = var1 + 1;
    }

    private void doMoreStuff(){
        int var3 = var1 - 1;
    }
}

Или просто создание экземпляров локальных переменных и передача их в качестве аргументов?

public class MyClass {  
    MyClass(){
        int var1 = doSomething();
        doSomethingElse(var1);
        doMoreStuff(var1);
    }

    private int doSomething(){
        int var = 2;
        return var;
    }

    private void doSomethingElse(int var){
        int var2 = var + 1;
    }

    private void doMoreStuff(int var){
        int var3 = var - 1;
    }
}

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



1
Я не думаю, что кто-то еще указал, что размещение промежуточных результатов в переменных экземпляра может затруднить параллелизм из-за возможности конфликта между потоками для этих переменных.
Сденхэм

Ответы:


107

Я удивлен, что это еще не упоминалось ...

Это зависит , если var1это на самом деле часть вашего объекта состояния .

Вы предполагаете, что оба эти подхода верны и это просто вопрос стиля. Вы неправы.

Это полностью о том, как правильно моделировать.

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


7
@mucaho: transientне имеет к этому никакого отношения, потому что transientречь идет о постоянном состоянии, как в частях объекта, которые сохраняются, когда вы делаете что-то вроде сериализации объекта. Например, хранилище резервных копий ArrayList является transientважным, хотя и крайне важным для состояния ArrayList, потому что при сериализации ArrayList вы хотите сохранить только ту часть резервного хранилища, которая содержит фактические элементы ArrayList, а не свободное пространство. в конце зарезервировано для дальнейшего добавления элемента.
user2357112 поддерживает Monica

4
Добавление к ответу - если var1это необходимо для пары методов, но не является частью состояния MyClass, возможно, пришло время поместить var1эти методы в другой класс, который будет использоваться MyClass.
Майк Партридж

5
@CandiedOrange Мы говорим о правильном моделировании здесь. Когда мы говорим «часть состояния объекта», мы не говорим о буквальных фрагментах кода. Мы обсуждаем концептуальное понятие состояния, каким должно быть состояние объекта, чтобы правильно моделировать понятия. «Срок полезного использования», вероятно, является самым решающим фактором в этом.
jpmc26

4
@CandiedOrange Next и Previous, очевидно, являются публичными методами. Если значение var1 относится только к последовательности частных методов, оно явно не является частью постоянного состояния объекта и, следовательно, должно передаваться в качестве аргументов.
Taemyr

2
@CandiedOrange Вы уточнили, что имели в виду, после двух комментариев. Я говорю, что вы легко могли бы получить в первом ответе. Кроме того, да, я забыл, что есть нечто большее, чем просто объект; Я согласен, что иногда методы частного экземпляра имеют цель. Я просто не мог довести это до ума. РЕДАКТИРОВАТЬ: Я только что понял, что вы на самом деле не первый человек, который ответил мне. Виноват. Я был озадачен тем, почему один ответ был кратким, одно предложение без ответа, в то время как следующий действительно объяснял вещи.
Фонд Моника иск

17

Я не знаю, какая из них более распространена, но я бы всегда делал последнее. Он более четко связывает поток данных и время жизни, и он не переполняет каждый экземпляр вашего класса полем, единственное релевантное время жизни которого - во время инициализации. Я бы сказал, что первое просто сбивает с толку и значительно усложняет проверку кода, поскольку я должен учитывать возможность изменения любого метода var1.


7

Вы должны максимально уменьшить область действия ваших переменных (и разумно). Не только в методах, но и вообще.

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


5

Какой стиль лучше (переменная экземпляра против возвращаемого значения) в Java

Есть другой стиль - используйте контекст / состояние.

public static class MyClass {
    // Hold my state from one call to the next.
    public static final class State {
        int var1;
    }

    MyClass() {
        State state = new State();
        doSomething(state);
        doSomethingElse(state);
        doMoreStuff(state);
    }

    private void doSomething(State state) {
        state.var1 = 2;
    }

    private void doSomethingElse(State state) {
        int var2 = state.var1 + 1;
    }

    private void doMoreStuff(State state) {
        int var3 = state.var1 - 1;
    }
}

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

Это шаблон, который также хорошо работает в распределенной / серверной системе, где некоторые детали должны сохраняться при вызовах. Вы можете хранить данные пользователя, соединения с базой данных и т. Д. В stateобъекте.


3

Это о побочных эффектах.

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

Побочный эффект подхода

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

Вы выпускаете переменную из области видимости, потому что она используется в двух разных частных областях. Не потому, что это нужно в том объеме, в котором вы его размещаете. Это может сбивать с толку. «Глобалы злые!» уровень запутанности. Это может сработать, но это не будет хорошо масштабироваться. Работает только в малом. Нет больших объектов. Нет длинных цепочек наследования. Не вызывайте эффекта йо-йо .

Функциональный подход

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

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

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

Риск побочных эффектов

Тем не менее, я знаю, откуда твой вопрос. Я работал под жалкой йо йо «ИНГ наследство , которое мутирует переменной экземпляра на нескольких уровнях , в нескольких методов и ушел беличьей пытается следовать за ним. Это риск.

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

Побочные эффекты

Это также ограничивает. Чистые функции не имеют побочных эффектов. Это может быть хорошо, но это не объектно-ориентированный. Большая часть объектной ориентации - это способность ссылаться на контекст вне метода. Делать это, не пропуская глобалы повсюду - это сила ООП. Я получаю гибкость глобального, но это приятно содержится в классе. Я могу вызвать один метод и мутировать каждую переменную экземпляра сразу, если мне нравится. Если я сделаю это, я обязан, по крайней мере, дать методу имя, которое прояснит, к чему он стремится, чтобы люди не удивились, когда это произойдет. Комментарии также могут помочь. Иногда эти комментарии оформляются как «условия публикации».

Недостаток функциональных частных методов

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

Пост условия

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

Вывод

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


1
«Некоторые переменные экземпляра используются только для связи между закрытыми методами от вызова к вызову». Это кодовый запах. Когда переменные экземпляра используются таким образом, это признак того, что класс слишком велик. Извлеките эти переменные и методы в новый класс.
Кевин Клайн

2
Это действительно не имеет никакого смысла. Хотя ФП может написать код в функциональной парадигме (и это, как правило, было бы хорошо), это, очевидно, не является контекстом вопроса. Попытка сообщить оператору о том, что они могут избежать сохранения состояния объекта путем изменения парадигм, на самом деле не имеет значения ...
jpmc26

@kevincline пример OP имеет только одну переменную экземпляра и 3 метода. По сути, это уже было извлечено в новый класс. Не то, чтобы пример делал что-нибудь полезное. Размер класса не имеет ничего общего с тем, как ваши частные вспомогательные методы взаимодействуют друг с другом.
MagicWindow,

@ jpmc26 OP уже показал, что они могут избежать сохранения var1в качестве переменной состояния путем изменения парадигм. Область видимости класса не просто где хранится состояние. Это также ограничивающая область. Это означает, что есть две возможные причины для размещения переменной на уровне класса. Вы можете утверждать, что делаете это только для охватывающей области, а не государство - зло, но я говорю, что это компромисс с артерией, которая также является злом. Я знаю это, потому что я должен был поддерживать код, который делает это. Некоторые были сделаны хорошо. Некоторым был кошмар. Граница между ними не государственная. Это читабельность.
MagicWindow,

Правило состояния улучшает удобочитаемость: 1) избегать того, чтобы промежуточные результаты операций выглядели как состояние 2) избегать сокрытия зависимостей методов. Если арность метода высока, то он либо неснижаемо и искренне представляет сложность этого метода, либо текущий дизайн имеет ненужную сложность.
Сденхэм

1

Первый вариант выглядит неинтуитивным и потенциально опасным для меня (представьте, по какой-то причине кто-то делает ваш приватный метод публичным).

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


0

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

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

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


0

Давайте попробуем пример, который делает что-то. Прости меня, так как это javascript, а не java. Дело должно быть таким же.

Посетите https://blockly-games.appspot.com/pond-duck?lang=en , перейдите на вкладку javascript и вставьте это:

hunt = lock(-90,1), 
heading = -135;

while(true) {
  hunt()  
  heading += 2
  swim(heading,30)
}

function lock(direction, width) {
  var dir = direction
  var wid = width
  var dis = 10000

  //hunt
  return function() {
    //randomize() //Calling this here makes the state of dir meaningless
    scanLock()
    adjustWid()
    if (isSpotted()) {
      if (inRange()) {
        if (wid <= 4) {
          cannon(dir, dis)
        }
      }
    } else {
      if (!left()) {
        right()
      }
    }
  }

  function scanLock() {
    dis = scan(dir, wid);
  }

  function adjustWid() {
    if (inRange()) {
      if (wid > 1)
        wid /= 2;
    } else {
      if (wid < 16) {
        wid *= 2; 
      }
    }
  }

  function isSpotted() {
    return dis < 1000;
  }

  function left() {
    dir += wid;
    scanLock();
    return isSpotted();
  }

  function right() {
    dir -= wid*2;
    scanLock();
    return isSpotted()
  }

  function inRange() {
    return dis < 70;
  }

  function randomize() {
    dir = Math.random() * 360
  }
}

Следует заметить , что dir, widи disне получают ходило много. Вы также должны заметить, что код в функции, которая lock()возвращает, очень похож на псевдокод. Ну, это актуальный код. Все же это очень легко читать. Мы могли бы добавить передачу и присваивание, но это добавило бы беспорядок, который вы никогда не увидите в псевдокоде.

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

Конечно, теперь мы могли бы dirперейти в область видимости сейчас, но не без необходимости загромождать наш псевдоподобный код с передачей и настройкой.

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

Нельзя сказать, что они не могут превратиться в спагетти, как кошмар. Но тогда, что не может?

Удачной охоты на уток.


1
Внутренние / внешние функции - это парадигма, отличная от описанной в OP. - Я согласен с тем, что компромисс между локальными переменными во внешней функции и передаваемых аргументах внутренним функциям аналогичен частным переменным в классе, передаваемом аргументами в частные функции. Однако, если вы сделаете это сравнение, время жизни объекта будет соответствовать одному запуску функции, поэтому переменные во внешней функции являются частью постоянного состояния.
Taemyr

@Taemyr ОП описывает приватные методы, вызываемые внутри публичного конструктора, которые для меня очень похожи на внутренние внутри внешнего. Состояние не является действительно «постоянным», если вы наступаете на него каждый раз при входе в функцию. Иногда состояние используется как общее место, методы могут писать и читать. Это не значит, что его нужно сохранять. Тот факт, что он сохраняется в любом случае, не является чем-то, от чего ДОЛЖНО зависеть.
candied_orange

1
Он постоянен, даже если вы наступаете на него каждый раз, когда избавляетесь от объекта.
Taemyr

1
Хорошие моменты, но выражение их в JavaScript действительно запутывает обсуждение. Кроме того, если вы удалите синтаксис JS, вы получите объект контекста, который будет передан (закрытие внешней функции)
fdreger,

1
@sdenham Я утверждаю, что есть разница между атрибутами класса и промежуточными промежуточными результатами операции. Они не эквивалентны. Но одно может храниться в другом. Это, конечно, не должно быть. Вопрос в том, должен ли он когда-либо быть. Да, есть семантические и пожизненные проблемы. Есть также проблемы арности. Вы не думаете, что они достаточно важны. Отлично. Но говорить, что использование уровня класса для чего-либо, кроме состояния объекта, является неправильным моделированием, настаивает на одном способе моделирования. Я сохранил профессиональный код, который сделал это по-другому.
candied_orange
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.