Является ли инкапсуляция еще одним из ООП слонов?

97

Инкапсуляция говорит мне сделать все или почти все поля приватными и выставить их через getters / setters. Но теперь появляются такие библиотеки, как Lombok, которые позволяют нам раскрывать все приватные поля одной короткой аннотацией @Data. Он создаст геттеры, сеттеры и конструкторы настроек для всех приватных полей.

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

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

В чем смысл всего этого цикла? И имеет ли смысл инкапсуляция в программировании в реальной жизни?

Гангнус
источник
19
"It will create getters, setters and setting constructors for all private fields."- То , как вы описываете этот инструмент, это звучит , как он будет поддерживать инкапсуляцию. (По крайней мере, в свободном, автоматизированном, несколько анемичном смысле.) Так в чем именно проблема?
Дэвид
78
Инкапсуляция скрывает внутренние реализации объекта за его публичным контрактом (обычно интерфейсом). Методы получения и установки делают прямо противоположное - они раскрывают внутренности объекта, поэтому проблема заключается в методах получения / установки, а не в инкапсуляции.
22
Классы данных @VinceEmigh не имеют инкапсуляции . Их экземпляры являются значениями в том смысле, в каком они являются примитивами
Caleth
10
@VinceEmigh с использованием JavaBeans - это не OO , это процедурный процесс . То, что литература называет их «объектами», является ошибкой истории.
Caleth
28
Я много думал об этом на протяжении многих лет; Я думаю, что это тот случай, когда цель ООП отличалась от реализации. После изучения SmallTalk стало ясно, какой должна быть инкапсуляция ООП (т. Е. Каждый класс подобен независимому компьютеру с методами в качестве общего протокола), хотя по причинам, которые мне пока неизвестны, он определенно завоевал популярность, чтобы сделать эти объекты getter / setter, которые не предлагают концептуальной инкапсуляции (они ничего не скрывают, ничего не управляют и не несут никакой ответственности, кроме данных), и все же они все еще используют свойства.
JRH

Ответы:

82

Если вы предоставляете все свои атрибуты с помощью методов получения / установки, вы получаете только структуру данных, которая все еще используется в Си или любом другом процедурном языке. Это не инкапсуляция, и Lombok просто делает работу с процедурным кодом менее болезненной. Получатели / сеттеры так же плохи, как и обычные открытые поля. На самом деле нет никакой разницы.

И структура данных не является объектом. Если вы начнете создавать объект с написания интерфейса, вы никогда не добавите получатели / установщики в интерфейс. Разоблачение ваших атрибутов приводит к процедурному коду спагетти, где манипулирование данными происходит вне объекта и распространяется по всей базе кода. Теперь вы имеете дело с данными и манипулируете ими, а не общаетесь с объектами. С геттерами / сеттерами у вас будет процедурное программирование, управляемое данными, где манипуляции с этим выполняются прямым императивным способом. Получить данные - сделать что-то - установить данные.

В ООП инкапсуляция - это слон, если все сделано правильно. Вы должны инкапсулировать состояние и детали реализации, чтобы объект имел полный контроль над этим. Логика будет сосредоточена внутри объекта и не будет распространяться по всей базе кода. И да - инкапсуляция все еще важна в программировании, так как код будет более понятным.

редактирует

Посмотрев на продолжающиеся дискуссии, я хочу добавить несколько вещей:

  • Неважно, сколько ваших атрибутов вы выставляете через геттеры / сеттеры и насколько тщательно вы это делаете. Быть более избирательным не сделает ваш код ООП с инкапсуляцией. Каждый раскрываемый вами атрибут приведет к некоторой процедуре, работающей с этими открытыми данными в обязательном порядке. Вы будете просто распространять свой код медленнее с большей избирательностью. Это не меняет ядра.
  • Да, в пределах границ системы вы получаете данные из других систем или базы данных. Но эти данные - просто еще одна точка инкапсуляции.
  • Объекты должны быть надежными . Вся идея объектов заключается в том, чтобы нести ответственность, так что вам не нужно отдавать приказы, которые являются прямыми и обязательными. Вместо этого вы просите объект сделать то, что он делает хорошо, через контракт. Вы можете безопасно делегировать часть действующих на объект. Объекты инкапсулируют состояние и детали реализации.

Поэтому, если мы вернемся к вопросу, почему мы должны это делать. Рассмотрим этот простой пример:

public class Document {
    private String title;

    public String getTitle() {
        return title;
    }
}

public class SomeDocumentServiceOrHandler {

    public void printDocument(Document document) {
        System.out.println("Title is " + document.getTitle());
    }
}

Здесь у нас есть Document, раскрывающий внутреннюю детали с помощью getter, и у нас есть внешний процедурный код в printDocumentфункции, который работает с этим вне объекта. Почему это плохо? Потому что теперь у вас есть только код в стиле C. Да, это структурировано, но в чем разница? Вы можете структурировать функции C в разных файлах и с именами. И эти так называемые слои делают именно это. Класс обслуживания - это просто набор процедур, которые работают с данными. Этот код менее понятен и имеет много недостатков.

public interface Printable {
    void print();
}

public final class PrintableDocument implements Printable {
    private final String title;

    public PrintableDocument(String title) {
        this.title = title;
    }

    @Override
    public void print() {
        System.out.println("Title is " + title);
    }
}

Сравните с этим. Теперь у нас есть контракт, и детали реализации этого контракта скрыты внутри объекта. Теперь вы можете действительно протестировать этот класс, и этот класс инкапсулирует некоторые данные. Как это работает с этими данными - вопрос объекта. Для того, чтобы поговорить с объектом сейчас вам нужно попросить его напечатать себя. Это инкапсуляция, и это объект. Вы получите всю мощь внедрения зависимостей, насмешек, тестирования, отдельных обязанностей и множества преимуществ с ООП.

Избассар Толеген
источник
Комментарии не для расширенного обсуждения; этот разговор был перенесен в чат .
maple_shaft
1
«Геттеры / сеттеры так же плохи, как простые открытые поля» - это неправда, по крайней мере, во многих языках. Обычно вы не можете переопределить простой доступ к полям, но можете переопределить методы получения / установки. Это дает универсальность дочерним классам. Это также позволяет легко изменить поведение классов. Например, вы можете начать с метода получения / установки, поддерживаемого частным полем, а затем перейти к другому полю или вычислить значение из других значений. Ни одно не может быть сделано с простыми полями. Некоторые языки позволяют полям иметь, по сути, автоматические методы получения и установки, но Java не является таким языком.
Kat
Эх, все еще не убежден. Ваш пример в порядке, но просто обозначает другой стиль кодирования, а не «истинно правильный», который не существует. Имейте в виду, что большинство языков в настоящее время являются мультипарадигмами и редко являются чисто ОО или чисто процедурными. Придерживаться "чистых ОО-концепций". Например, вы не можете использовать DI в вашем примере, потому что вы связали логику печати с подклассом. Печать не должна быть частью документа - printableDocument предоставит свою печатаемую часть (через геттер) для PrinterService.
Т. Сар - Восстановить Монику
Правильный подход к этому OO, если бы мы шли к подходу «Pure OO», использовали бы что-то, реализующее абстрактный класс PrinterService, запрашивающий, через сообщение, что, черт возьми, должно быть напечатано - используя своего рода GetPrintablePart. Объектом, который реализует PrinterService, может быть любой тип принтера - печать в PDF, на экран, в TXT ... Ваше решение делает невозможным замену логики печати на что-то другое, что в итоге приводит к большей связанности и меньшему объему обслуживания. чем твой "неправильный" пример.
Т. Сар - Восстановить Монику
Итог: геттеры и сеттеры не являются злом и не нарушают ООП - люди, которые не понимают, как их использовать. В качестве примера вы можете привести пример из учебника, в котором люди не понимают, как работает DI. Пример, который вы «исправили», был уже с поддержкой DI, отсоединен, мог быть легко смоделирован ... Действительно - помните всю вещь «Prefer Composition over Inheritance»? Один из столпов ОО? Вы просто бросили его в окно, как вы рефакторинг кода. Это не сработало бы в любом серьезном обзоре кода.
Т. Сар - Восстановить Монику
76

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

Смысл в том, что вы не должны этого делать .

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

Не надо просто давать все поля геттерам и сеттерам по умолчанию!

Это полностью противоречит духу спецификации JavaBeans, которая, по иронии судьбы, является источником концепции общедоступных методов получения и установки.

Но если вы посмотрите на спецификацию, то увидите, что создание геттеров и сеттеров было очень избирательным, и в нем говорится о свойствах «только для чтения» (без сеттера) и «только для записи» (нет геттер).

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

Итак, вы: инкапсуляция означает, что вы предоставляете только те функциональные возможности, которые вам действительно нужны. Но если вы не думаете о том, что вам нужно раскрыть, и просто волей-неволей разоблачаете все, следуя некоторой синтаксической трансформации, тогда, конечно, это не совсем инкапсуляция.

Майкл Боргвардт
источник
20
«[G] etters и setters не обязательно простой доступ» - я думаю, что это ключевой момент. Если ваш код обращается к полю по имени, вы не сможете изменить его поведение позже. Если вы используете obj.get (), вы можете.
Дэн Амброджио
4
@jrh: я никогда не слышал, чтобы это называлось плохой практикой в ​​Java, и это довольно распространено.
Майкл Боргвардт
2
@MichaelBorgwardt Интересно; немного не по теме, но я всегда удивлялся, почему Microsoft не рекомендует делать это для C #. Я предполагаю, что эти рекомендации подразумевают, что в C # единственное, что можно использовать для установки сеттеров, - это преобразовать значение, данное в какое-то другое значение, используемое внутри, таким способом, который не может дать сбой или имеет недопустимое значение (например, наличие свойства PositionInches и свойства PositionCentimeters где он автоматически конвертируется внутри мм)? Не очень хороший пример, но это лучшее, что я могу придумать на данный момент.
JRH
2
@jrh, что источник говорит не делать это для добытчиков, в отличие от сеттеров.
Капитан Мэн
3
@ Гангнус, а ты действительно видишь, что кто-то скрывает какую-то логику внутри геттера / сеттера? Мы настолько привыкли к этому, что getFieldName()стали для нас автоматическим контрактом. Мы не будем ожидать какого-то сложного поведения за этим. В большинстве случаев это просто прямое нарушение инкапсуляции.
Избассар Толеген
17

Я думаю, что суть вопроса объясняется вашим комментарием:

Я полностью согласен с вашей мыслью. Но где-то мы должны загружать объекты данными. Например, из XML. И современные платформы, поддерживающие его, делают это через геттеры / сеттеры, тем самым снижая качество нашего кода и нашего мышления. Ломбок, действительно, неплохой сам по себе, но само его существование показывает, что у нас есть что-то плохое.

Проблема в том, что вы смешиваете модель постоянных данных с активной моделью данных.

Приложение обычно имеет несколько моделей данных:

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

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

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

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

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

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

Матье М.
источник
3
Я бы не стал создавать геттеры и сеттеры для объектов связи. Я бы просто обнародовал поля. Зачем создавать больше работы, чем полезно?
user253751
3
@immibis: Я согласен в целом, однако, как отмечается в OP, некоторые фреймворки требуют получения / установки, и в этом случае вы просто должны соблюдать. Обратите внимание, что OP использует библиотеку, которая создает их автоматически с применением одного атрибута, так что для него это мало работы.
Матье М.
1
@Gangnus: Идея в том, что геттеры / сеттеры изолированы от граничного (I / O) слоя, где нормально пачкаться, но остальная часть приложения (чистый код) не загрязнена и остается элегантной.
Матье М.
2
@ kubanczyk: насколько я знаю, официальное определение - бизнес-модель объекта. Здесь это соответствует тому, что я назвал «внутренней моделью данных», моделью данных, на которой вы выполняете логику в «чистом» ядре вашего приложения.
Матье М.
1
Это прагматичный подход. Мы живем в гибридном мире. Если бы внешние библиотеки могли знать, какие данные передавать конструкторам спецификации, то у нас были бы только спецификации.
Адриан
12

Рассмотрим следующее ..

У вас есть Userкласс со свойством int age:

class User {
    int age;
}

Вы хотите увеличить это, чтобы Userиметь дату рождения, а не только возраст. Используя геттер:

class User {
    private int age;

    public int getAge() {
        return age;
    }
}

Мы можем поменять int ageполе на более сложное LocalDate dateOfBirth:

class User {
    private LocalDate dateOfBirth;

    public int getAge() {
        LocalDate now = LocalDate.now();
        int year = ...; // calculate using dateOfBirth and now
        return year;
    }

    // other behaviors can now make use of dateOfBirth
}

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

Само поле инкапсулировано.


Теперь, чтобы очистить проблемы ..

@DataАннотация Lombok похожа на классы данных Kotlin .

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

Инкапсуляция используется, чтобы скрыть значения или состояние объекта структурированных данных внутри класса

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

Сделаете ли вы вывод, что развитие предприятия плохо из-за использования бинов? Конечно, нет! Требования отличаются от требований стандартной разработки. Можно ли злоупотреблять бобами? Конечно! Они постоянно подвергаются насилию!

Lombok также поддерживает @Getterи @Setterсамостоятельно - использует то, что требуют ваши требования.

Винс Эмиг
источник
2
Это не связано с наложением @Dataаннотации на тип, который по замыслу удаляет все поля
Caleth
1
Нет, поля не скрыты . Потому что все может прийти иsetAge(xyz)
Caleth
9
@Caleth Поля , которые являются скрытыми. Вы действуете так, как будто сеттеры не могут иметь предварительные и последующие условия, что является очень распространенным случаем использования сеттеров. Вы действуете так, как будто свойство field ==, которое даже в таких языках, как C #, просто не соответствует действительности, так как оба имеют тенденцию поддерживаться (как в C #). Поле может быть перемещено в другой класс, его можно поменять местами для более сложного представления ...
Винс Эми
1
И я говорю, что это не важно
Caleth
2
@Caleth Как это не важно? Это не имеет никакого смысла. Вы говорите, что инкапсуляция не применяется, потому что вы чувствуете, что это не важно в этой ситуации, даже если она применяется по определению и имеет ли она варианты использования?
Винс Эми
11

Инкапсуляция говорит мне сделать все или почти все поля приватными и выставить их через getters / setters.

Это не то, как инкапсуляция определяется в объектно-ориентированном программировании. Инкапсуляция означает, что каждый объект должен быть похож на капсулу, чья внешняя оболочка (общедоступные API) защищает и регулирует доступ к своей внутренней части (частные методы и поля) и скрывает ее от просмотра. Скрывая внутренние компоненты, вызывающие абоненты не зависят от внутренних, что позволяет изменять внутренние компоненты без изменения (или даже перекомпиляции) вызывающих абонентов. Кроме того, инкапсуляция позволяет каждому объекту применять свои собственные инварианты, делая только безопасные операции доступными для вызывающих.

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

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

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

Частично это связано с исторической аварией. Первое, что бросается в глаза, это то, что в Java выражения вызова метода и выражения доступа к полю синтаксически различаются на сайте вызова, то есть замена доступа к полю вызовом получателя или установщика нарушает API класса. Поэтому, если вам может понадобиться метод доступа, вы должны написать его сейчас или иметь возможность нарушить API. Это отсутствие поддержки свойств на уровне языка резко контрастирует с другими современными языками, особенно C # и EcmaScript .

Забастовка 2 состоит в том, что спецификация JavaBeans определяла свойства как получатели / установщики, поля не были свойствами. В результате большинство ранних корпоративных сред поддерживало методы получения / установки, но не поля. Это давно ушло в прошлое ( API персистентности Java (JPA) , проверка бинов , архитектура Java для привязки XML (JAXB) , Джексонвсе поля поддержки к настоящему времени просто прекрасны), но старые учебники и книги продолжают задерживаться, и не все знают, что все изменилось. Отсутствие поддержки свойств на уровне языка все еще может быть проблемой в некоторых случаях (например, потому что отложенная загрузка JPA отдельных сущностей не срабатывает при чтении открытых полей), но в основном открытые поля работают просто отлично. Иными словами, моя компания пишет все DTO для своих REST API с открытыми полями (в конце концов, она не становится общедоступной, передаваемой через Интернет :-).

Тем не менее, Lombok @Dataделает больше, чем просто генерирует геттеры / сеттеры: он также генерирует toString(), hashCode()и equals(Object), что может быть весьма полезным.

И имеет ли смысл инкапсуляция в программировании в реальной жизни?

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

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

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

Резюме

геттеры / сеттеры предлагают довольно плохую инкапсуляцию.

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

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

меритон - забастовка
источник
1
применяет свои инварианты? Это английский? Не могли бы вы объяснить это не англичанину, пожалуйста? Не будь слишком сложным - некоторые люди здесь недостаточно хорошо знают английский.
Гангнус
Мне нравится твоя основа, мне нравятся твои мысли (+1), но не практический результат. Я бы предпочел использовать личное поле и некоторую специальную библиотеку для загрузки данных для них по отражению. Я только не понимаю, почему вы выставляете свой ответ как аргумент против меня?
Гангнус
@Gangnus Инвариант - это логическое условие, которое не изменяется (в смысле нет, а значение -варианта меняется / изменяется). Многие функции имеют правила, которые должны быть истинными до и после их выполнения (так называемые предварительные условия и постусловия), иначе в коде есть ошибка. Например, функция может потребовать, чтобы ее аргумент не был равен нулю (предварительное условие), и может выдать исключение, если ее аргумент равен нулю, потому что этот случай будет ошибкой в ​​коде (т. Е. В компьютерной науке код нарушает инвариант).
Pharap
@Gangus: Не совсем уверен, где отражение входит в эту дискуссию; Lombok - это процессор аннотаций, то есть плагин для компилятора Java, который генерирует дополнительный код во время компиляции. Но, конечно, вы можете использовать ломбок. Я просто говорю, что во многих случаях публичные поля работают так же хорошо, и их проще настроить (не все компиляторы обнаруживают процессоры аннотаций автоматически ...).
Меритон - забастовка
1
@Gangnus: Инварианты одинаковы в математике и CS, но в CS есть дополнительная перспектива: с точки зрения объекта, инварианты должны быть применены и установлены. С точки зрения вызывающего этого объекта инварианты всегда верны.
меритон
7

Я действительно задавал себе этот вопрос.

Хотя это не совсем так. Распространенность IMO геттеров / сеттеров обусловлена ​​спецификацией Java Bean, которая требует этого; так что, если хотите, это в значительной степени особенность не объектно-ориентированного программирования, а бин-ориентированного программирования. Разница между ними заключается в уровне абстракции, в котором они существуют; Бины - это скорее системный интерфейс, т.е. на более высоком уровне. Они абстрагируются от основ ОО, или, по крайней мере, предназначены для того, чтобы, как всегда, все происходило слишком часто.

Я бы сказал, что несколько прискорбно, что эта вещь Bean, которая вездесуща в программировании на Java, не сопровождается добавлением соответствующей функции языка Java - я думаю о чем-то вроде концепции свойств в C #. Для тех, кто не знает об этом, это языковая конструкция, которая выглядит следующим образом:

class MyClass {
    string MyProperty { get; set; }
}

В любом случае, практическая реализация все еще очень выигрывает от инкапсуляции.

daniu
источник
В Python есть похожая особенность, в которой свойства могут иметь прозрачные методы получения / установки. Я уверен, что еще больше языков имеют похожие функции.
JAB
1
«Распространенность IMO геттеров / сеттеров обусловлена ​​спецификацией Java Bean, которая требует этого». Да, вы правы. И кто сказал, что это необходимо? Причина, пожалуйста?
Гангнус
2
Способ C # абсолютно такой же, как у Lombok. Я не вижу никакой реальной разницы. Более практичное удобство, но явно плохое по замыслу.
Гангнус
1
@Gangnis Спецификация требует этого. Почему? Посмотрите мой ответ для конкретного примера того, почему геттеры - это не то же самое, что экспонирование полей - попробуйте достичь того же самого без геттера.
Винс Эми
@ VinceEmigh gangnUs, пожалуйста :-). (гуляющий, нус - орех). А как насчет использования get / set только там, где оно нам конкретно нужно, и массовой загрузки в частные поля путем отражения в других случаях?
Гангнус
6

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

Простой ответ здесь: вы абсолютно правы. Получатели и установщики устраняют большую часть (но не все) значения инкапсуляции. Это не значит, что всякий раз, когда у вас есть метод get и / или set, который нарушает инкапсуляцию, но если вы слепо добавляете методы доступа ко всем закрытым членам вашего класса, вы делаете это неправильно.

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

Геттеры являются сеттерами повсеместно распространены в программировании на Java, потому что концепция JavaBean была предложена как способ динамического связывания функциональности с предварительно созданным кодом. Например, у вас может быть форма в апплете (кто-нибудь помнит те?), Которая будет проверять ваш объект, находить все свойства и отображать поля as. Затем пользовательский интерфейс может изменять эти свойства на основе пользовательского ввода. Вы, как разработчик, просто беспокоитесь о написании класса и размещаете там валидацию или бизнес-логику и т. Д.

введите описание изображения здесь

Использование примеров Bean

Это не страшная идея как таковая, но я никогда не был большим поклонником подхода в Java. Это просто идет против зерна. Используйте Python, Groovy и т. Д. Что-то, что поддерживает такой подход более естественно.

JavaBean отчасти вышел из-под контроля, потому что он создал JOBOL, то есть Java-разработчиков, которые не понимают ОО. По сути, объекты стали не более чем пакетами данных, и вся логика была написана снаружи длинными методами. Поскольку это воспринималось как нормальное, такие люди, как вы и я, которые задают этот вопрос, считаются обманщиками. В последнее время я видел сдвиг, и это не такая уж посторонняя позиция

XML-привязка - крепкий орешек. Это, вероятно, не хорошее поле битвы для противостояния JavaBeans. Если вам нужно построить эти JavaBeans, постарайтесь не допустить их в настоящий код. Рассматривайте их как часть уровня сериализации.

JimmyJames
источник
2
Самые конкретные и мудрые мысли. +1. Но почему бы не написать группу классов, каждый из которых будет массово загружать / сохранять частные поля в / из какой-то внешней структуры? JSON, XML, другой объект. Он может работать с POJO или, может быть, только с полями, помеченными для этого аннотацией. Теперь я думаю об этой альтернативе.
Гангнус
@Gangnus На предположение, потому что это означает много лишнего написания кода. Если используется отражение, то должен быть написан только один кусок кода сериализации, и он может сериализовать любой класс, который следует определенному шаблону. C # поддерживает это через [Serializable]атрибут и его родственников. Зачем писать 101 специфические методы / классы сериализации, когда вы можете просто написать один, используя рефлексию?
Pharap
@Pharap Мне очень жаль, но мой комментарий слишком короток для меня. "используя отражение"? И какой смысл скрывать такие разные вещи в одном классе? Не могли бы вы объяснить, что вы имеете в виду?
Гангнус
@Gangnus Я имею в виду рефлексию , способность кода проверять свою собственную структуру. В Java (и других языках) вы можете получить список всех функций, которые предоставляет класс, а затем использовать их как способ извлечения данных из класса, необходимого для сериализации их, например, в XML / JSON. Использование рефлексии, которая будет единовременной, вместо постоянного создания новых методов для сохранения структур для каждого класса. Вот простой пример Java .
Pharap
@Pharap Это именно то, о чем я говорил. Но почему вы называете это «использованием»? И альтернатива, которую я упомянул в первом комментарии, остается здесь - должны ли (не) упакованные поля иметь специальные аннотации или нет?
Гангнус
5

Сколько мы можем сделать без добытчиков? Можно ли их полностью удалить? Какие проблемы это создает? Можем ли мы даже запретить returnключевое слово?

Оказывается, вы можете многое сделать, если вы готовы сделать работу. Как же тогда информация выходит из этого полностью инкапсулированного объекта? Через сотрудника.

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

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

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

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

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

Это может выглядеть так:

Введите описание изображения здесь

class Interactor implements InputPort {
    OutputPort out;
    int y = 0;

    Interactor(OutputPort out){
        this.out = out;
    }

    void accumulate(int x) {
        y = y + x;
        out.showAsImage(y);
    }
}

Если вы можете так кодировать, тогда использование геттеров - выбор. Не необходимое зло.

candied_orange
источник
Да, такие геттеры / сеттеры имеют большой смысл. Но вы понимаете, что это о чем-то еще? :-) +1 все равно.
Гангнус
1
@Gangnus Спасибо. Есть куча вещей, о которых вы могли бы говорить. Если бы ты хотел хотя бы назвать их, я мог бы пойти к ним.
candied_orange
5

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

Зачем использовать геттеры и сеттеры, а не открытые поля?

Потому что геттеры / сеттеры обеспечивают инкапсуляцию. Если вы представляете значение как открытое поле, а затем переходите к вычислению значения на лету, вам необходимо изменить все клиенты, обращающиеся к полю. Очевидно, это плохо. Это деталь реализации, хранится ли какое-либо свойство объекта в поле, генерируется на лету или извлекается откуда-то еще, поэтому различие не должно быть открыто для клиентов. Геттер / сеттер-сеттер решают эту проблему, поскольку они скрывают реализацию.

Но если метод получения / установки просто отражает основное частное поле, не так ли плохо?

Нет! Дело в том, что инкапсуляция позволяет изменить реализацию, не влияя на клиентов. Поле могло бы все еще быть прекрасным способом сохранить ценность, пока клиенты не должны знать или заботиться.

Но не генерирует ли сам получатель / установщик из полей, нарушающих инкапсуляцию?

Нет инкапсуляции все еще там! @Dataаннотации - это просто удобный способ написать пары getter / setter, которые используют базовое поле. Для точки зрения клиента это похоже на обычную пару геттер / сеттер. Если вы решите переписать реализацию, вы все равно можете сделать это, не затрагивая клиента. Таким образом, вы получаете лучшее из обоих миров: инкапсуляция и краткий синтаксис.

Но некоторые говорят, что геттеры / сеттеры всегда плохи!

Существует отдельная дискуссия, в которой некоторые считают, что шаблон getter / setter всегда плох, независимо от базовой реализации. Идея состоит в том, что вы не должны устанавливать или получать значения от объектов, а любое взаимодействие между объектами должно моделироваться как сообщения, в которых один объект просит другой объект сделать что-то. Это в основном кусочек догмы с первых дней объектно-ориентированного мышления. Теперь мы думаем, что для определенных шаблонов (например, объектов значений, объектов передачи данных) геттеры / сеттеры могут быть вполне подходящими.

JacquesB
источник
Инкапсуляция должна позволить нам полиморфизм и безопасность. Получает / устанавливает разрешить первый, но сильно против второго.
Гангнус
Если вы говорите, что я где-то использую догму, скажите, пожалуйста, что из моего текста является догмой. И не забывайте, что некоторые мысли, которыми я пользуюсь, мне не нравятся или с ними не согласны. Я использую их для демонстрации противоречия.
Гангнус
1
"GangNus" :-). Неделю назад я работал над проектом, буквально полным вызова геттеров и сеттеров в нескольких слоях. Это абсолютно небезопасно, и еще хуже, оно поддерживает людей в небезопасном кодировании. Я рад, что сменил работу достаточно быстро, чтобы люди привыкли к этому. Так что я не думаю, я это вижу .
Гангнус
2
@jrh: я не знаю, есть ли формальное объяснение как таковое, но в C # есть встроенная поддержка свойств (методы получения / установки с более приятным синтаксисом) и анонимных типов, которые являются типами только с свойствами и без поведения. Таким образом, разработчики C # сознательно отклонились от метафоры обмена сообщениями и принципа «говори, не спрашивай». Разработка C # начиная с версии 2.0, кажется, больше вдохновлена ​​функциональными языками, чем традиционной чистотой ОО.
JacquesB
1
@jrh: Я думаю, сейчас, но я думаю, что C # весьма вдохновлен функциональными языками, где данные и операции более разделены. В распределенных архитектурах перспективы также сместились с ориентированных на сообщения (RPC, SOAP) на ориентированные на данные (REST). И базы данных, которые раскрывают и оперируют данными (не объектами «черного ящика»), превалируют. Короче говоря, я думаю, что акцент сместился с сообщений между черными ящиками на открытые модели данных
JacquesB
3

Инкапсуляция имеет цель, но она также может быть использована неправильно или злоупотреблять.

Рассмотрим что-то вроде Android API, в котором есть классы с десятками (если не сотнями) полей. Открывая эти поля, пользователь API усложняет навигацию и использование, а также дает пользователю ложное представление о том, что он может делать с этими полями все, что захочет, что может конфликтовать с тем, как они должны использоваться. Таким образом, инкапсуляция хороша в этом смысле для удобства обслуживания, удобства использования, читабельности и предотвращения сумасшедших ошибок.

С другой стороны, POD или простые старые типы данных, такие как структура из C / C ++, в которой все поля являются открытыми, также могут быть полезны. Наличие бесполезных методов получения / установки, подобных тем, которые генерируются аннотацией @data в Lombok, - это просто способ сохранить «шаблон инкапсуляции». Одна из немногих причин, по которой мы делаем «бесполезные» методы получения / установки в Java, заключается в том, что методы предоставляют контракт .

В Java вы не можете иметь поля в интерфейсе, поэтому вы используете геттеры и сеттеры для указания общего свойства, которое есть у всех разработчиков этого интерфейса. В более поздних языках, таких как Kotlin или C #, мы видим концепцию свойств как полей, для которых вы можете объявить установщик и получатель. В конце концов, бесполезные методы получения / установки являются более наследием, с которым приходится жить Java, если только Oracle не добавит в него свойства. Например, Kotlin, который является еще одним языком JVM, разработанным JetBrains, имеет классы данных, которые в основном выполняют то же, что и аннотация @data в Lombok.

Также вот несколько примеров:

class DataClass 
{
    private int data;

    public int getData() { return data; }
    public void setData(int data) { this.data = data; } 
}

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

class DataClass implements IDataInterface
{
    private int data;

    @Override public int getData() { return data; }
    @Override public void setData(int data) { this.data = data; }
}

Это хороший пример инкапсуляции. Инкапсуляция используется для обеспечения исполнения контракта, в данном случае IDataInterface. Цель инкапсуляции в этом примере - заставить потребителя этого класса использовать методы, предоставляемые интерфейсом. Несмотря на то, что метод получения и установки не делает ничего особенного, мы теперь определили общую черту между DataClass и другими реализациями IDataInterface. Таким образом, у меня может быть такой метод:

void doSomethingWithData(IDataInterface data) { data.setData(...); }

Теперь, когда речь идет об инкапсуляции, я считаю важным также рассмотреть проблему синтаксиса. Я часто вижу, что люди жалуются на синтаксис, необходимый для обеспечения инкапсуляции, а не самой инкапсуляции. Один пример, который приходит на ум, - от Кейси Муратори (вы можете увидеть его напыщенную речь здесь ).

Предположим, у вас есть класс игрока, который использует инкапсуляцию и хотите переместить свою позицию на 1 единицу. Код будет выглядеть так:

player.setPosX(player.getPosX() + 1);

Без инкапсуляции это выглядело бы так:

player.posX++;

Здесь он утверждает, что инкапсуляция приводит к гораздо большему количеству печатания без дополнительных преимуществ, и во многих случаях это может быть правдой, но обратите внимание. Аргумент против синтаксиса, а не самой инкапсуляции. Даже в таких языках, как C, в которых отсутствует концепция инкапсуляции, вы часто будете видеть переменные в структурах с префиксом или с суффиксом '_' или 'my' или любым другим, чтобы показать, что они не должны использоваться потребителем API, как если бы они были частный.

В том-то и дело, что инкапсуляция может помочь сделать код более понятным и простым в использовании. Рассмотрим этот класс:

class VerticalList implements ...
{
    private int posX;
    private int posY;
    ... //other members

    public void setPosition(int posX, int posY)
    {
        //change position and move all the objects in the list as well
    }
}

Если бы переменные были общедоступны в этом примере, тогда потребитель этого API был бы озадачен тем, когда использовать posX и posY и когда использовать setPosition (). Скрывая эти детали, вы помогаете потребителю лучше использовать ваш API интуитивно понятным способом.

Хотя синтаксис является ограничением во многих языках. Однако более новые языки предлагают свойства, которые дают нам приятный синтаксис публикуемых членов и преимущества инкапсуляции. Вы найдете свойства в C #, Kotlin, даже в C ++, если вы используете MSVC. Вот пример в Котлине.

class VerticalList: ... {var posX: Int set (x) {field = x; ...} var posY: Int set (y) {field = y; ...}}

Здесь мы достигли того же, что и в примере с Java, но мы можем использовать posX и posY, как если бы они были открытыми переменными. Когда я пытаюсь изменить их значение, будет выполнено тело setter ().

Например, в Kotlin это будет эквивалент Java Bean с реализованными getter, setters, hashcode, equals и toString:

data class DataClass(var data: Int)

Обратите внимание, что этот синтаксис позволяет нам делать Java Bean в одну строку. Вы правильно заметили проблему, которая существует в языке, подобном Java, при реализации инкапсуляции, но это вина Java не в самой инкапсуляции.

Вы сказали, что используете @Data Lombok для генерации геттеров и сеттеров. Обратите внимание на имя @Data. Он в основном предназначен для использования в классах данных, которые хранят только данные и предназначены для сериализации и десериализации. Придумайте что-то вроде файла сохранения из игры. Но в других сценариях, например, с элементом пользовательского интерфейса, вам определенно нужны сеттеры, потому что простого изменения значения переменной может быть недостаточно для получения ожидаемого поведения.

BananyaDev
источник
Не могли бы вы привести пример неправильного использования инкапсуляции? Это может быть интересно. Ваш пример скорее против отсутствия инкапсуляции.
Гангнус
1
@Gangnus Я добавил несколько примеров. Бесполезная инкапсуляция, как правило, используется неправильно, и, по моему опыту, разработчики API слишком стараются заставить вас использовать API определенным образом. Я не приводил пример для этого, потому что мне было нелегко представить. Если я найду один, я определенно добавлю его. Я думаю, что большинство комментариев против инкапсуляции на самом деле против синтаксиса инкапсуляции, а не самой инкапсуляции.
BananyaDev
Спасибо за редактирование. Я нашел небольшую опечатку: «публикация» вместо «публика».
17
2

Инкапсуляция дает вам гибкость . Разделяя структуру и интерфейс, она позволяет изменять структуру без изменения интерфейса.

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

glennsl
источник
Хотя теоретически это хорошая идея, помните, что создание 2-й версии API вызывает исключение, которого не имела 1-я версия, что ужасно сломало бы пользователей вашей библиотеки или класса (и, возможно, молча!); Я немного скептически отношусь к тому, что в большинстве этих классов данных могут быть внесены какие-либо серьезные изменения "за кулисами".
JRH
Извините, это не объяснение. Тот факт, что использование полей в интерфейсах запрещено, проистекает из идеи инкапсуляции. И сейчас мы говорим об этом. Вы опираетесь на обсуждаемые факты. (обратите внимание, я не говорю ни о том, ни о твоих мыслях, а только о том, что они не могут быть использованы здесь в качестве аргументов)
Гангнус
@Gangnus Слово «интерфейс» имеет значение за пределами только ключевого слова Java: dictionary.com/browse/interface
glennsl
@jrh Это совсем другая проблема. Вы можете использовать его для аргументации против инкапсуляции в определенных контекстах , но это никоим образом не делает недействительными аргументы для него.
Гленнсл
1
Старая инкапсуляция без массы get / set дала нам не только полиморфизм, но и безопасность. И масса устанавливает / получает научить нас к плохой практике.
Гангнус
2

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

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

Имеет ли смысл иметь открытые поля, или методы получения / установки, или методы транзакций более высокого уровня, или передачу сообщений, зависит от природы моделируемого домена. В книге Akka Concurrency (которую я могу порекомендовать, даже если она несколько устарела) вы найдете пример, который иллюстрирует это, и я сокращу его здесь.

Рассмотрим класс User:

public class User {
  private String first = "";
  private String last = "";

  public String getFirstName() {
    return this.first;
  }
  public void setFirstName(String s) {
    this.first = s;
  }

  public String getLastName() {
    return this.last;
  }
  public void setLastName(String s) {
    this.last = s;
  }
}

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

Однако представьте, что это должно быть обеспечено в многопоточном контексте. Предположим, один поток периодически читает имя:

System.out.println(user.getFirstName() + " " + user.getLastName());

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

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

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

public class UserName {
   public final String first;
   public final String last;
   public UserName(String first, String last) { ... }
}

public class User
   private UserName name;
   public UserName getName() { return this.name; }
   public setName(UserName n) { this.name = n; }
}

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

В чем смысл всего этого цикла? И имеет ли смысл инкапсуляция в программировании в реальной жизни?

В этом цикле вы видите попытки применить решения, подходящие для определенного набора обстоятельств, слишком широко. Подходящий уровень инкапсуляции требует понимания моделируемой области и применения правильного уровня инкапсуляции. Иногда это означает, что все поля являются открытыми, иногда (как в приложениях Akka) это означает, что у вас вообще нет общедоступного API, за исключением одного метода получения сообщений. Однако сама концепция инкапсуляции, означающая скрытие внутренних компонентов за стабильным API, является ключом к программному обеспечению в масштабе, особенно в многопоточных системах.

Джори Себрехтс
источник
Фантастика. Я бы сказал, что вы подняли обсуждение на уровень выше. Возможно два. Действительно, я думал, что понимаю инкапсуляцию, а иногда и лучше, чем стандартные книги, и действительно очень боялся, что мы практически теряем ее из-за некоторых принятых обычаев и технологий, и это было реальным предметом моего вопроса (возможно, не сформулированным в хороший путь), но вы показали мне действительно новые стороны инкапсуляции. Я определенно не очень хорош в многозадачности, и вы показали мне, насколько это может быть вредно для понимания других основных принципов.
Гангнус
Но я не могу отметить свой пост в ответе, ибо речь идет не о данных массовой загрузки в / из объектов, проблемы из - за которой появляются такие платформы , как бобы и Ломбки. Может быть, вы могли бы развить свои мысли в этом направлении, если вы можете уделить время? Я сам еще не переосмыслил последствия вашей мысли для этой проблемы. И я не уверен , что я достаточно подходит для этого (помните, плохой многопоточность фон: - [)
Гангнус
Я не использовал lombok, но, насколько я понимаю, это способ реализации определенного уровня инкапсуляции (геттеры / сеттеры в каждом поле) с меньшим набором текста. Он не меняет идеальную форму API для вашей проблемной области, это всего лишь инструмент для более быстрого написания.
Джори Себрехтс
1

Я могу вспомнить один случай использования, где это имеет смысл. У вас может быть класс, к которому вы изначально обращаетесь через простой API-интерфейс getter / setter. Позже вы расширяете или модифицируете так, чтобы он больше не использовал те же поля, но все еще поддерживал тот же API .

Несколько надуманный пример: точка, которая начинается как декартова пара с p.x()и p.y(). Позже вы создаете новую реализацию или подкласс, который использует полярные координаты, поэтому вы также можете вызывать p.r()и p.theta(), но ваш клиентский код, который вызывает p.x()и p.y()остается действительным. Сам класс прозрачно преобразуется из внутренней полярной формы, то есть, y()теперь return r * sin(theta);. (В этом примере установка только x()или y()не имеет большого смысла, но все же возможна.)

В этом случае вы можете сказать: «Рад, что я потрудился автоматически объявить методы получения и установки вместо того, чтобы обнародовать поля, иначе мне пришлось бы взломать мой API».

Davislor
источник
2
Это не так приятно, как вы видите. Изменение внутреннего физического представления действительно делает объект другим. И плохо, что они выглядят одинаково, потому что у них разные качества. Система Декарта не имеет особых точек, полярная система имеет такие.
Гангнус
1
Если вам не нравится этот конкретный пример, вы наверняка можете подумать о других, которые действительно имеют несколько эквивалентных представлений, или о более новом представлении, которое можно преобразовать в более старое и обратно.
Дэвислор
Да, вы можете очень продуктивно использовать их для презентации. В пользовательском интерфейсе его можно широко использовать. Но ты не заставляешь меня отвечать на мой собственный вопрос? :-)
Гангнус
Так эффективнее, не так ли? :)
Дэвислор
0

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

Нет абсолютно никакого смысла. Однако тот факт, что вы задаете этот вопрос, демонстрирует, что вы не поняли, что делает Lombok, и что вы не понимаете, как писать ОО-код с инкапсуляцией. Давайте немного перемотать ...

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

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

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

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

Да, было бы возможно сделать эту переменную общедоступной. В этой конкретной версии кода он будет функционально идентичен. Но вернемся к тому, почему мы используем функции: «Мы можем захотеть изменить то, как класс работает под поверхностью». Если вы сделаете вашу переменную общедоступной, вы ограничите свой код сейчас и навсегда, чтобы эта открытая переменная была интерфейс. Если вы используете функции или если вы используете Lombok для автоматического создания этих функций для вас, вы можете в любое время изменить базовые данные и базовую реализацию.

Это делает это более ясным?

Грэхем
источник
Вы говорите о вопросах первокурсника SW. Мы уже в другом месте. Я бы никогда не сказал этого и даже дал бы вам плюс за умные ответы, если бы вы не были так невежливы в форме своего ответа.
Гангнус
0

Я на самом деле не Java-разработчик; но следующее в значительной степени не зависит от платформы.

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

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

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

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

Джошуа
источник
Добро пожаловать в мир Java. (C # тоже). *вздох*. Но что касается использования get / set для скрытия внутреннего представления, будьте осторожны. Это касается только внешнего формата, это нормально. Но если речь идет о математике, или, что еще хуже, физике или другим реальным вещам, различное внутреннее представление имеет разные качества. а именно мой комментарий к softwareengineering.stackexchange.com/a/358662/44104
Гангнус
@Gangnus: этот пример наиболее неудачный. У нас их было довольно много, но расчет всегда был точным.
Джошуа
-1

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

Скажем, через несколько месяцев после выпуска ваш клиент сообщает вам, что для одного из полей класса иногда задаются отрицательные значения, хотя в этом случае оно должно быть не более 0. Используя открытые поля, вам придется искать каждое назначение этого поля во всей вашей кодовой базе, чтобы применить функцию ограничения к значению, которое вы собираетесь установить, и помните, что вам всегда придется делать это при изменении этого поля, но это отстой. Вместо этого, если бы вы уже использовали методы получения и установки, вы могли бы просто изменить свой метод setField (), чтобы гарантировать, что это ограничение всегда применяется.

Теперь проблема с «устаревшими» языками, такими как Java, состоит в том, что они поощряют использование методов для этой цели, что просто делает ваш код бесконечно более многословным. Писать больно и трудно, поэтому мы использовали IDE, так или иначе смягчающие эту проблему. Большинство IDE автоматически генерирует для вас методы получения и установки, а также скрывает их, если не указано иное. Lombok делает еще один шаг и просто генерирует их процедурно во время компиляции, чтобы сделать ваш код более гладким. Однако другие, более современные языки просто так или иначе решили эту проблему. Языки Scala или .NET, например,

Например, в VB .NET или C # вы могли бы просто сделать все поля, предназначенные для простого -не побочного эффекта, получателями и установщиками, просто открытыми полями, а затем сделать их закрытыми, изменить их имя и предоставить свойство с прежним именем поле, где вы можете точно настроить поведение доступа к полю, если вам это нужно. С Lombok, если вам нужно точно настроить поведение метода получения или установки, вы можете просто удалить эти теги, когда это необходимо, и написать свой собственный с новыми требованиями, зная, что вам не придется ничего реорганизовывать в других файлах.

По сути, способ, которым ваш метод обращается к полю, должен быть прозрачным и равномерным. Современные языки позволяют определять «методы» с одним и тем же синтаксисом доступа / вызова поля, поэтому эти изменения могут быть выполнены по требованию, не задумываясь об этом на раннем этапе разработки, но Java вынуждает вас выполнять эту работу заранее, потому что это делает не имеют этой функции. Все, что делает Lombok, - это экономит ваше время, потому что используемый вами язык не позволяет вам сэкономить время для метода «на всякий случай».

HorriblePerson
источник
В этих «современных» языках API выглядит как доступ к полю, foo.barно он может быть обработан вызовом метода. Вы утверждаете, что это превосходит «устаревший» способ отображения API как вызовы методов foo.getBar(). Кажется, мы согласны с тем, что открытые поля проблематичны, но я утверждаю, что «устаревшая» альтернатива превосходит «современную», поскольку весь наш API симметричен (это все вызовы методов). В «современном» подходе мы должны решить, какие вещи должны быть свойствами, а какие - методами, которые все усложняют (особенно, если мы используем отражение!).
Warbo
1
1. Скрывать физику не всегда так хорошо. смотри мой комментарий к softwareengineering.stackexchange.com/a/358662/44104 . 2. Я не говорю о том, насколько сложным или сложным является создание геттера / сеттера. C # имеет абсолютно ту же проблему, о которой мы говорим. Отсутствие инкапсуляции из-за плохого решения массовой загрузки информации. 3. Да, решение может быть основано на синтаксисе, но, пожалуйста, другими способами, чем вы упоминаете. Это плохо, у нас здесь большая страница, заполненная объяснениями. 4. Решение не должно основываться на синтаксисе. Это может быть сделано в рамках Java.
Гангнус
-1

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

Да, это противоречиво. Я впервые столкнулся со свойствами в Visual Basic. До этого на других языках, по моему опыту, не было никаких оберток собственности вокруг полей. Просто общественные, частные и защищенные поля.

Свойства были своего рода инкапсуляцией. Я понимал, что свойства Visual Basic - это способ контролировать и манипулировать выводом одного или нескольких полей, скрывая явное поле и даже его тип данных, например, отправляя дату в виде строки в определенном формате. Но даже тогда нельзя «скрывать состояние и раскрывать функциональность» с точки зрения более крупного объекта.

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

Так почему же мы не использовали настоящие методы? Потому что это был Visual BasicVBScript ) (оооо! Аааа!), Кодирование для масс (!), И это было все в ярости. И, таким образом, в конечном итоге доминирует идиократия.

radarbob
источник
Да, свойства для пользовательского интерфейса имеют особый смысл, они являются публичными и симпатичными представлениями полей. Хорошая точка зрения.
Гангнус
«Так почему же мы / они просто не использовали реальные методы?» Технически они сделали в .Net версии. Свойства являются синтаксическим сахаром для функций get и set.
Pharap
"идиотократия" ? Разве вы не имеете в виду " идиосинкразия "?
Питер Мортенсен
Я имел ввиду идиократию
радаробоб