Який стиль кращий (мінливість екземпляра проти повернення вартості) на 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 підтримує Моніка

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

5
@CandiedOrange Ми говоримо про правильне моделювання тут. Коли ми говоримо "частина стану об'єкта", ми не говоримо про буквальні фрагменти коду. Ми обговорюємо концептуальне поняття стану, яким має бути стан об'єкта для належного моделювання понять. "Строк корисного використання", ймовірно, найбільший вирішальний фактор у цьому.
jpmc26

4
@CandiedOrange Наступний і Попередній - це, очевидно, загальнодоступні методи. Якщо значення var1 є релевантним лише для послідовності приватних методів, воно, очевидно, не є частиною стійкого стану об'єкта, і тому його слід передавати як аргументи.
Taemyr

2
@CandiedOrange Ви пояснили, що ви мали на увазі, після двох коментарів. Я кажу, що ви могли легко отримати в першій відповіді. Крім того, так, я забув, що є більше, ніж просто об'єкт; Я погоджуюся, що іноді методи приватного примірника мають мету. Я просто не міг довести жодного розуму. EDIT: Я щойно зрозумів, що ти насправді не перший, хто відповів на мене. Моє ліжко. Мене збентежило, чому одна відповідь - це прокляття, одне речення, невідповідь, а наступна насправді пояснює речі.
Фонд позову Моніки

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повинно зберігатись, нічого не говорить про те, що ви повинні використовувати, якщо для кожного перехідного значення воно може отримати, перш ніж воно досягне стану, яке ви хочете зберегти між публічними дзвінками. Це означає, що ви все одно можете встановитиvar1 екземпляр, використовуючи лише функціональні методи.

Отже, частина держави чи ні, ви все ще можете використовувати будь-який підхід.

У цих прикладах 'var1' настільки інкапсульовано, крім того, що ваш налагоджувач знає, що він існує. Я здогадуюсь, що ви зробили це навмисно, тому що не хочете нас упереджувати. На щастя, мені все одно.

Ризик побічних ефектів

Це означає, що я знаю, звідки бере ваше запитання. Я працював у жалюгідному йо-йо «ІНГ спадок , яке мутує змінної примірника на декількох рівнях , в декількох методів і пішов білячої намагається слідувати за ним. Це ризик.

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

Перебіг побічних ефектів

Це також обмежує. Чисті функції не мають побічних ефектів. Це може бути хорошою справою, але це не орієнтоване на об'єкти. Велика частина орієнтації на об'єкт - це здатність посилатися на контекст поза методом. Виконання цього, не протікаючи всесвітня глобалізація, є силою OOP. Я отримую гнучкість глобальної, але вона гарно міститься в класі. Я можу викликати один метод і мутувати кожну змінну екземпляра одразу, якщо мені подобається. Якщо я це зроблю, я зобов’язаний хоча б дати методу назву, яка дає зрозуміти, що це, щоб люди не здивувалися, коли це станеться. Коментарі можуть також допомогти. Іноді ці коментарі формалізуються як "умови публікації".

Мінус функціональних приватних методів

Функціональний підхід дозволяє зрозуміти деякі залежності. Якщо ви не володієте чистою функціональною мовою, вона не може виключати приховані залежності. Ви не знаєте, дивлячись лише на підпис методів, що він не приховує від вас побічного ефекту в решті коду. Ви просто цього не робите.

Опублікувати умовні умови

Якщо ви та всі інші в команді надійно задокументуєте побічні ефекти (умови до / після публікації) у коментарях, то виграш від функціонального підходу набагато менший. Так, я знаю, мрію.

Висновок

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


1
"Деякі змінні екземпляра використовуються лише для зв'язку між приватними методами від дзвінка до виклику." Це кодовий запах. Коли змінні екземплярів використовуються таким чином, це знак, що клас занадто великий. Витягніть ці змінні та методи в новий клас.
кевін клайн

2
Це справді не має сенсу. Хоча ОП може записати код у функціональній парадигмі (і це, як правило, було б добре), це очевидно не є контекстом питання. Намагаючись сказати ОП, що вони можуть уникнути збереження стану об'єкта, змінюючи парадигми, насправді не актуально ...
jpmc26

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

@ jpmc26 OP вже показав, що вони можуть уникнути зберігання var1як змінної стану, змінюючи парадигми. Обсяг класу НЕ лише там, де зберігається стан. Це також є додатковою сферою застосування. Це означає, що є дві можливі мотивації розмістити змінну на рівні класу. Ви можете стверджувати, що це робиться лише для сфери, що охоплює, а не держава - це зло, але я кажу, що це торгівля з сухістю, яка також є злою. Я знаю це, тому що мені довелося підтримувати код, який це робить. Деякі були зроблені добре. У деяких був кошмар. Рядок між ними не є державним. Це читабельність.
MagicWindow

Правило стану підвищує читабельність: 1) уникати тимчасових результатів операцій, схожих на стан 2) уникати приховування залежностей методів. Якщо стислість методу висока, то він або безповоротно і справді представляє складність цього методу, або сучасний дизайн має зайву складність.
sdenham

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
Внутрішня / зовнішня функції є іншою парадигмою, ніж те, що описано в ОП. - Я погоджуюся, що компроміс між локальними змінними зовнішньої функції протиставляє аргументи внутрішнім функціям аналогічно приватним змінним класу, який передає аргументи приватним функціям. Однак якщо зробити це порівняння, термін експлуатації об'єкта відповідав би одному виконанню функції, тож змінні зовнішньої функції є частиною постійного стану.
Taemyr

@Taemyr OP описує приватні методи, що називаються у публічному конструкторі, який мені здається подібним до внутрішніх. Стан насправді не "стійкий", якщо ви наступаєте на нього щоразу, коли ви вводите функцію. Іноді стан використовується як методи спільного використання можуть писати та читати. Це не означає, що його потрібно наполегливо зберігати. Те, що воно все одно зберігається, НЕ МОЖЕ залежати.
candied_orange

1
Він стійкий, навіть якщо ви наступаєте на нього щоразу, коли ви розпоряджаєтесь предметом.
Taemyr

1
Хороші моменти, але висловлення їх у JavaScript справді пригнічує дискусію. Крім того, якщо ви знімете синтаксис JS, ви перейдете до контекстного об'єкта, який передається навколо (закриття зовнішньої функції)
fdreger

1
@sdenham Я стверджую, що існує різниця між атрибутами класу і тимчасовими проміжними результатами операції. Вони не рівноцінні. Але одне може зберігатися в іншому. Це, звичайно, не повинно бути. Питання в тому, якщо це колись має бути. Так, є смислові та життєві питання. Також є проблеми з артійством. Ви не вважаєте, що вони досить важливі. Це добре. Але сказати, що використання рівня класу для будь-якого іншого, крім стану об'єкта, є неправильним моделюванням, наполягаючи на одному способі моделювання. Я зберігав професійний код, який робив це інакше.
candied_orange
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.