Використання державних фінальних, а не приватних одержувачів


51

Я бачу більшість непорушних POJO, написаних так:

public class MyObject {
    private final String foo;
    private final int bar;

    public MyObject(String foo, int bar) {
        this.foo = foo;
        this.bar = bar;
    }

    public String getFoo() {
        return foo;
    }

    public int getBar() {
        return bar;
    }
}

Але я схильний писати їх так:

public class MyObject {
    public final String foo;
    public final int bar;

    public MyObject(String foo, int bar) {
        this.foo = foo;
        this.bar = bar;
    }
}

Зауважте, посилання остаточні, тому Об'єкт залишається незмінним. Це дозволяє мені писати менше коду і дозволяє коротший (на 5 символів: getі ()) доступ.

Єдиний недолік, який я можу побачити, - якщо ви хочете змінити реалізацію getFoo()вниз, щоб зробити щось божевільне, ви не можете. Але реально, цього ніколи не відбувається, тому що Об'єкт незмінний; ви можете перевірити під час створення екземпляра, створити непорушні захисні копії під час створення екземпляра (див., наприклад, Гуаву ImmutableList) та підготуйте fooабо barоб'єкти готові до getвиклику.

Чи є якісь недоліки, яких мені не вистачає?

EDIT

Думаю, ще один недолік, якого мені не вистачає, - це бібліотеки серіалізації, що використовують роздуми над методами, починаючи з getабо is, але це досить жахлива практика ...


Якого біса? finalне робить змінну об'єкта незмінним. Я зазвичай використовую дизайн, де я визначаю finalполя перед створенням зворотного дзвінка, щоб зворотний виклик міг отримати доступ до цих полів. Звичайно, можна викликати всі методи, включаючи будь-які setXметоди.
Томаш Зато

@ TomášZato Там немає методів Setx на String, intабо MyObject. У першій версії finalпотрібно лише забезпечити, наприклад, інші методи, ніж конструктор у класі, не намагаються bar = 7;, наприклад. У другому варіанті, finalнеобхідно , щоб запобігти споживачів робити: MyObject x = new MyObject("hi", 5); x.bar = 7;.
Кори Кендалл

1
Добре, що ваше " Зауважте, що посилання є остаточними, тому Objectвсе ще незмінне. " Вводить в оману - таким чином, здається, ви вважаєте, що будь- final Objectяке незмінне, а це не так. Вибачте за непорозуміння.
Томаш Зато

3
Якби основні значення були змінними, приватна дорога + дорога доступу також не завадила б це - myObj.getFoo().setFrob(...).
Бені Чернявський-Паскін

Відповіді:


38

Чотири недоліки, про які я можу придумати:

  1. Якщо ви хочете мати форму читання та зміну одного і того ж об'єкта, загальним шаблоном є наявність об'єкта незмінного класу, який відкриває лише доступ до захищених змінних членів, а потім створити MutableEntity, який розширює його та додає сетери. Ваша версія перешкоджає цьому.
  2. Використання геттерів та сетерів дотримується конвенції JavaBeans. Якщо ви хочете використовувати свій клас як квасоля в технологіях, заснованих на власності, таких як JSTL або EL, вам потрібно викрити громадських жителів.
  3. Якщо ви хочете колись змінити реалізацію, щоб отримати значення або переглянути їх у базі даних, вам доведеться перефактурувати код клієнта. Підхід accessor / mutator дозволяє змінити лише реалізацію.
  4. Найменше здивування - коли я бачу змінні загальнодоступних екземплярів, я одразу шукаю, хто може його мутувати, і переживаю, що я відкриваю скриньку пандори, оскільки втрачається інкапсуляція. http://en.wikipedia.org/wiki/Principle_of_least_astonishment

Однак, ваша версія, безумовно, більш коротка. Якщо це був спеціалізований клас, який використовується лише в межах певного пакету (можливо, тут є корисна область пакету), то я можу вважати це одноразовим. Але я б не викривав таких основних API.


3
Чудові відповіді; У мене є незгоди з деякими з них, але я не впевнений, що цей сайт є найкращим форумом для обговорення. Коротше кажучи, 1) Я можу зламати інших, використовуючи форму, що змінюється, де вони вважають, що це незмінне (можливо, я повинен додати остаточний клас до своїх прикладів), 2) Я згоден, 3) Я б стверджував, що це вже не POJO в тому, що випадок, 4) Я зараз погоджуюсь, але, сподіваюся, дискусії на кшталт цього можуть змінити те, що найменше дивує :).
Кори Кендалл

2
5) Ви не можете безпечно виставляти змінні класи / типи, подібні до масивів - як масиви. Безпечний спосіб - кожен раз повертати копію від одержувача.
Завод-Муза

@ Clockwork-Muse ви можете, якщо вважаєте, що люди не ідіоти. Під час доступу до someObj.thisList.add () очевидно, що ви змінюєте стан інших об'єктів. З деякимObj.getThisList (). Add () невідомо. Потрібно запитати документацію або шукати джерело, але тільки з декларації методу сказати це неможливо.
kritzikratzi

2
@kritzikratzi - Проблема в тому, що ви не можете гарантувати, що ваш код не натрапить на ідіотів. У якийсь момент це станеться (що може навіть стати самим собою!), І сюрприз може стати величезною проблемою.
Завод-Муза

1
Що ж, дозволити успадковувати будь-який тип мутації від непорушного типу - це по суті неправильно. Пам'ятайте, що непорушний тип дає гарантію "я не змінююсь. Колись".
Дедуплікатор

26

Позбудьтеся також геттерів / сетерів, і ви все добре!

Це дуже суперечлива тема серед Java-програмістів.

У будь-якому випадку, є дві ситуації, де я використовую загальнодоступні змінні замість (!) Getters / setters:

  1. публічний фінал Для мене це сигнали "я непорушний" набагато краще, ніж просто геттер. Більшість IDE вказуватиме остаточний модифікатор з "F" під час автоматичного заповнення. На відміну від getters / setters, де вам доведеться шукати відсутність setXXX.
  2. публічний нефінал Я люблю це для класів даних. Я просто викриваю всі поля публічно. Ніяких геттерів, сетерів, конструкторів. Немає нічого. Менше, ніж піхо. Мені це одразу сигналізує: "дивись, я німий. Я тримаю дані, це все. ВАША робота ввести правильні дані всередину мене". Gson / JAXB / тощо. справляйтеся з цими заняттями просто чудово. Вони блаженство писати. Немає сумнівів у їх призначенні чи можливостях. І найголовніше: ви знаєте, що при зміні змінної немає побічних ефектів. ІМХО призводить до цього дуже стислих моделей даних з невеликими двозначностями, тоді як геттери і сетери мають цю величезну проблему, коли іноді всередині них відбувається магія.

5
Приємно раз на деякий час бачити певний розум :)
Navin

2
Поки не настане день, коли ваша модель розвивається, і одне із старих полів тепер може бути похідне від іншого. Оскільки ви хочете підтримувати зворотну сумісність, старе поле має залишатися, але замість того, щоб просто змінювати код геттера та сеттера, вам доведеться змінювати всюди, де це старе поле використовувалося для того, щоб воно відповідало новому представленню моделі. Легко писати .. так, звичайно ... кошмар, який потрібно підтримувати в довгостроковій перспективі ... Ось чому ми маємо геттер / сетер або ще краще у власниках C #.
Ньютопський

9

Словами мирянина:

  • Ви порушуєте інкапсуляцію , щоб зберегти кілька рядків коду. Це перемагає призначення OOD.
  • Код клієнта буде важко поєднаний з іменами членів вашого класу. Зчеплення погано. Вся мета OOD - запобігання зчеплення.
  • Ви також впевнені, що ваш клас ніколи не потребуватиме змін. Все змінюється. Зміни - єдине, що є постійним.

6
Як це порушення капсулювання? Обидві версії так само піддаються впливу навколишнього світу, як і одна до одної (з доступом лише для читання). Ви вірно ставитесь до жорсткої взаємозв'язку та негнучкості змін, хоча я не заперечуватиму про це.
KChaloux

4
@KChaloux "Ви порушуєте" порушення інкапсуляції "з" невдалою компіляцією коду та втратою часу на його виправлення ", це відбувається спочатку. Друге відбувається пізніше. Щоб зрозуміти: інкапсуляція в даний час вже порушена. Внутрішні часи вашого класу в даний час більше не інкапсульовані. Але клієнтський код порушиться в майбутньому, якщо клас зміниться в майбутньому, оскільки інкапсуляція була порушена в минулому. Є два різних "перерви", інкапсуляція сьогодні, клієнтський код завтра через відсутність інкапсуляції.
Тулен Кордова

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

5
Прагматичний: Як часто програмісти насправді рефакторують приватні імена полів, не перейменовуючи загальнодоступні геттери / сетери? З того, що я можу сказати, існує загальноприйнята конвенція для того, щоб називати їх однаковими, навіть якщо ця конвенція може бути результатом лише інструментів IDE, які генерують геттери та сетери з імен полів. Теоретичне: Однак з точки зору об'єктно-орієнтованого моделювання все, що публічне, є частиною публічного договору, а не внутрішньою деталізацією реалізації. Тож якщо хтось вирішить оприлюднити остаточне поле, чи не говорять вони, що це договір, який вони обіцяють виконати?
Thomas Jung

3
@ user949300 Скажи мені, які з них я можу навчитися.
Tulains Córdova

6

Один з можливих недоліків, які я можу побачити назовні - це те, що ви пов'язані з внутрішнім представленням даних у класі. Це, мабуть, не є величезною справою, але якщо ви використовуєте сетери та вирішите, що foo та bar будуть повернуті з якогось іншого класу, визначеного десь в іншому місці, класи, що споживають MyObject, не потрібно буде змінювати. Вам потрібно лише торкнутися MyObject. Однак, якщо ви використовуєте голі значення, то вам доведеться торкатися скрізь вашого MyObject, який я використовував.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.