Использование публичного финала, а не частных получателей

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например, Guava ) и получить объекты fooили, barготовые к getвызову.

Есть ли недостатки, которые я пропускаю?

РЕДАКТИРОВАТЬ

Полагаю, еще один недостаток, который мне не хватает, - это библиотеки сериализации, использующие рефлексию над методами, начинающимися с 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
Если бы базовые значения были изменяемыми, дорога private + accessors тоже не помешала бы - myObj.getFoo().setFrob(...).
Бени Чернявский-Паскин

Ответы:

38

Четыре недостатка, о которых я могу подумать:

  1. Если вы хотите иметь доступную только для чтения и изменяющуюся форму одной и той же сущности, общий шаблон - иметь неизменяемый класс Entity, который предоставляет только методы доступа с защищенными переменными-членами, а затем создает MutableEntity, который расширяет его и добавляет сеттеры. Ваша версия предотвращает это.
  2. Использование методов получения и установки придерживается соглашения JavaBeans. Если вы хотите использовать свой класс в качестве bean-компонента в технологиях, основанных на свойствах, таких как JSTL или EL, вам необходимо предоставить открытые методы получения.
  3. Если вы когда-нибудь захотите изменить реализацию для получения значений или поиска их в базе данных, вам придется реорганизовать клиентский код. Подход аксессора / мутатора позволяет вам только изменить реализацию.
  4. Наименьшее удивление - когда я вижу общедоступные переменные экземпляра, я сразу же ищу, кто может изменить его, и беспокоюсь, что я открываю ящик Пандоры, потому что инкапсуляция потеряна. http://en.wikipedia.org/wiki/Principle_of_least_astonishment

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

Brandon
источник
3
Отличные ответы; У меня есть разногласия с некоторыми из них, но я не уверен, что этот сайт - лучший форум для обсуждения. Короче говоря, 1) я могу сломать других, используя изменяемую форму, где они предполагают, что она неизменна (возможно, я должен добавить final к классу в моих примерах), 2) я согласен, 3) я бы сказал, что это больше не POJO в этом случай, 4) Я согласен на данный момент, но, надеюсь, такие обсуждения могут изменить то, что наименее удивительно :).
Кори Кендалл
2
5) Вы не можете безопасно выставлять изменчивые классы / типы - например, массивы. Безопасный способ - каждый раз возвращать копию с получателя.
Заводная муза
@ Clockwork-Muse вы можете, если считаете, что люди не идиоты. При доступе к someObj.thisList.add () очевидно, что вы изменяете состояние некоторых других объектов. С someObj.getThisList (). Add () это неизвестно. Вы должны спросить документацию или посмотреть источник, но из одного объявления метода невозможно сказать.
kritzikratzi
2
@kritzikratzi - проблема в том, что вы не можете гарантировать, что ваш код не попадет в идиотов. В какой-то момент это произойдет (что может даже оказаться самим собой!), И неожиданность может стать огромной проблемой.
Заводная муза
1
Что ж, позволить любому изменяемому типу наследовать от неизменяемого типа по своей сути неправильно. Помните, что неизменяемый тип дает гарантию: «Я не меняюсь. Всегда».
Дедупликатор
26

Избавьтесь от добытчиков / сеттеров тоже, и вы в порядке!

Это очень спорная тема среди Java-программистов.

В любом случае, есть две ситуации, где я использую публичные переменные вместо (!) Методов получения / установки:

  1. Публичный финал Для меня это сигнал «я неизменный» гораздо лучше, чем просто добытчик. Большинство IDE будут указывать окончательный модификатор с буквой «F» во время автозаполнения. В отличие от геттеров / сеттеров, где вы должны искать отсутствие setXXX.
  2. Публичный не финал, я люблю это для классов данных. Я просто выставляю все поля публично. Нет добытчиков, сеттеров, конструкторов. Нет, ничего. Меньше, чем Пойо. Для меня это сразу же сигнализирует: «Послушай, я тупой. Я держу данные, вот и все. Твоя работа - поместить в меня нужные данные». Gson / JAXB / и т.д.. справиться с этими классами просто отлично. Они блаженство, чтобы написать. Там нет сомнений относительно их цели или возможностей. И самое главное: вы знаете, что нет никаких побочных эффектов при изменении переменной. ИМХО, это приводит к очень кратким моделям данных с небольшим количеством неоднозначностей, тогда как геттеры и сеттеры сталкиваются с этой огромной проблемой, когда внутри них иногда происходит волшебство.
kritzikratzi
источник
5
Приятно видеть какое-то здравомыслие однажды некоторое время :)
Navin
2
Пока не наступит день, когда ваша модель будет развиваться, и одно из старых полей теперь может быть получено из другого. Поскольку вы хотели бы поддерживать обратную совместимость, старое поле должно остаться, но вместо простого изменения кода получателя и установщика вам придется менять везде, где использовалось это старое поле, чтобы оно соответствовало новому представлению модели. Легко написать ... да, конечно ... кошмар для поддержания в долгосрочной перспективе ... Вот почему у нас есть методы получения / установки или, что еще лучше, в средствах доступа к свойствам C #.
Ньютопия
9

По словам непрофессионала:

  • Вы нарушаете инкапсуляцию , чтобы сохранить несколько строк кода. Это побеждает цель ООД.
  • Код клиента будет жестко связан с именами ваших учеников. Сцепление это плохо. Вся цель OOD - предотвращение связи.
  • Вы также уверены, что ваш класс никогда не будет изменчивым. Вещи меняются. Изменение - единственное, что постоянно.
Тулаинс Кордова
источник
6
Как это нарушение инкапсуляции? Обе версии так же подвержены влиянию внешнего мира, как и другие (с доступом только для чтения). Вы правы насчет жесткой связи и негибкости перемен, но я не буду спорить об этом.
KChaloux
4
@KChaloux Вы путаете «нарушение инкапсуляции» с «кодом, который не компилируется и не тратит время на его исправление», сначала происходит одно. Второе случается позже. Чтобы было понятно: инкапсуляция уже нарушена в настоящем. Внутренности вашего класса больше не заключены в капсулу в настоящее время. Но клиентский код будет нарушен в будущем, если класс изменится в будущем, потому что инкапсуляция была нарушена в прошлом. Из-за отсутствия инкапсуляции есть два разных «перерыва»: инкапсуляция сегодня и клиентский код завтра.
Тулаинс Кордова
8
Технически, когда вы используете геттеры и т.д. вместо этого клиентский код будет жестко связан с именами методов. Посмотрите, сколько библиотек должно включать старые версии методов с «устаревшим» тегом / аннотацией, потому что старый код все еще зависит от них (и некоторые люди все еще могут использовать их, потому что им легче работать с ними, но вы не должен сделать это).
JAB
5
Прагматично: как часто программисты фактически реорганизуют имена частных полей, не переименовывая общедоступные методы получения / установки? Из того, что я могу сказать, существует общесоюзное соглашение о присвоении им одинаковых имен, даже если это соглашение может быть только результатом инструментов IDE, которые генерируют методы получения и установки из имен полей. Теоретически: однако, с точки зрения объектно-ориентированного моделирования, все публичное является частью публичного контракта, а не внутренней деталью реализации. Так что, если кто-то решит обнародовать финальное поле, не говорят ли они, что это контракт, который они обещают выполнить?
Томас Юнг
3
@ user949300 Скажи мне, какие из них, чтобы я мог узнать.
Тулаинс Кордова
6

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

ipaul
источник