Частное против.Публичные члены на практике (насколько важна инкапсуляция?) [закрыто]

StackOverflow https://stackoverflow.com/questions/99688

  •  01-07-2019
  •  | 
  •  

Вопрос

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

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

Соблюдаете ли вы правило в целом?Меняется ли ваш ответ в зависимости от того, написан ли этот код для себя или для себя?чтобы его использовали другие?Есть ли более тонкие причины, по которым я упускаю эту путаницу?

Это было полезно?

Решение

Это зависит.Это один из тех вопросов, которые необходимо решать прагматично.

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

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

На самом деле все сводится к тому, управляют ли структурой глаголы (методы) или данные.

Другие советы

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

Да, инкапсуляция имеет значение.Раскрытие базовой реализации делает (как минимум) две ошибки:

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

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

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

С уважением, Олли

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

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

Пример: Я пишу код, который обновляет переменную, и мне нужно быть абсолютно уверенным, что графический интерфейс изменяется, чтобы отразить это изменение. Самый простой способ — добавить метод доступа (он же «Setter»), который вызывается вместо обновления данных. обновляется.

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

По опыту, единственный раз, когда вы захотите сделать переменную-член общедоступной и исключить методы Getter и Setter, — это если вы хотите абсолютно ясно дать понять, что ее изменение не будет иметь побочных эффектов;особенно если структура данных проста, например, класс, который просто содержит две переменные как пару.

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

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

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

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

Свойства C# «имитируют» общедоступные поля.Выглядит довольно круто, а синтаксис действительно ускоряет создание методов get/set.

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

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

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


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

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

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

private Foo foo;
public Foo getFoo() {}
public void setFoo(Foo foo) {}

Это немного сложно оправдать.

Инкапсуляция важна, когда выполняется хотя бы одно из следующих условий:

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

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

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

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

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

Конечно, если вы пишете что-то вроде библиотеки, вам нужен как можно более ясный и четкий интерфейс, чтобы другие могли программировать на его основе.

Подгоните инструмент к работе...недавно я увидел такой код в моей текущей кодовой базе:

private static class SomeSmallDataStructure {
    public int someField;
    public String someOtherField;
}

А затем этот класс использовался внутри для простой передачи нескольких значений данных.Это не всегда имеет смысл, но если у вас есть только ДАННЫЕ без методов и вы не предоставляете их клиентам, я считаю это весьма полезным шаблоном.

Последний раз я использовал это на странице JSP, где у меня отображалась таблица данных, определенная вверху декларативно.Итак, изначально это было в нескольких массивах, по одному массиву на поле данных...это закончилось тем, что в коде было довольно сложно разобраться, поскольку поля не находились рядом друг с другом по определению и отображались вместе...поэтому я создал простой класс, подобный приведенному выше, который объединит его...в результате получился ДЕЙСТВИТЕЛЬНО читаемый код, намного лучше, чем раньше.

Мораль...иногда вам следует рассмотреть «принятые плохие» альтернативы, если они могут сделать код более простым и легким для чтения, если вы все продумаете и примете во внимание последствия...не принимайте слепо ВСЁ, что слышите.

Тем не менее...публичные геттеры и сеттеры во многом эквивалентны публичным полям...по крайней мере, по существу (есть немного больше гибкости, но это по-прежнему плохой шаблон для применения к КАЖДОМУ вашему полю).

Даже в стандартных библиотеках Java есть случаи общественные поля.

Когда я придаю объектам смысл, они Полегче к использовать и легче поддерживать.

Например:Person.Hand.Grab(как быстро, сколько);

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

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

Однако для меня «инкапсуляция» — это либо:

  • процесс перегруппировки нескольких предметов в контейнер
  • сам контейнер перегруппирует элементы

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

  • список детей, представляющих детей
  • карта города учитывает детей от разных родителей
  • объект Children (не Child), который предоставит необходимую информацию (например, общее количество детей)

Здесь у вас есть три разных типа инкапсуляции: два представлены контейнером низкого уровня (список или карта), один — объектом.

Принимая такие решения, вы не

  • сделайте эту инкапсуляцию публичной, защищенной или частной:этот выбор «сокрытия информации» еще предстоит сделать
  • сделать полную абстракцию (вам необходимо уточнить атрибуты объекта Children, и вы можете решить создать объект Child, который будет хранить только релевантную информацию с точки зрения системы налогоплательщиков)
    Абстракция — это процесс выбора, какие атрибуты объекта имеют отношение к вашей системе, а какие следует полностью игнорировать.

Итак, моя точка зрения такова:
Этот вопрос можно назвать так:
Частное против.Представители общественности на практике (насколько важно сокрытие информации?)

Но это всего лишь мои 2 цента.Я полностью уважаю тот факт, что инкапсуляцию можно рассматривать как процесс, включающий решение о «сокрытии информации».

Однако я всегда стараюсь различать «абстракцию» — «инкапсуляцию» — «скрытие или видимость информации».

@VonC

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

Здесь я попытался обосновать, что сокрытие информации является важной частью этого определения:http://www.edmundkirwan.com/encap/s2.html

С уважением,

Эд.

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

Наличие как геттеров, так и сеттеров равнозначно тому, что поле является общедоступным (когда геттеры и сеттеры тривиальны/генерируются автоматически).Иногда может быть лучше просто объявить поля общедоступными, чтобы код был более простым, если только вам не нужен полиморфизм или фреймворк не требует методов get/set (и вы не можете изменить фреймворк).

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

Когда я создаю графический интерфейс приложения, я стараюсь сохранить поведение графического интерфейса в одном классе (FooModel), чтобы его можно было легко протестировать, и иметь визуализацию графического интерфейса в другом классе (FooView), который можно протестировать. только вручную.Представление и модель соединяются с помощью простого связующего кода;когда пользователь меняет значение поля x, просмотр вызывает setX(String) в модели, что, в свою очередь, может вызвать событие об изменении какой-то другой части модели, и представление получит обновленные значения из модели с помощью геттеров.

В одном проекте есть модель GUI, которая имеет 15 методов получения и установки, из которых только 3 метода get являются тривиальными (такими, что IDE может их генерировать).Все остальные содержат некоторые функциональные возможности или нетривиальные выражения, например следующие:

public boolean isEmployeeStatusEnabled() {
    return pinCodeValidation.equals(PinCodeValidation.VALID);
}

public EmployeeStatus getEmployeeStatus() {
    Employee employee;
    if (isEmployeeStatusEnabled()
            && (employee = getSelectedEmployee()) != null) {
        return employee.getStatus();
    }
    return null;
}

public void setEmployeeStatus(EmployeeStatus status) {
    getSelectedEmployee().changeStatusTo(status, getPinCode());
    fireComponentStateChanged();
}

На практике я всегда следую только одному правилу: «ни один размер не подходит всем».

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

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

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

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

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

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

При этом мне нравится, как Python справляется с этим.Переменные-члены по умолчанию являются общедоступными.Если вы хотите скрыть их или добавить проверку, предусмотрены методы, но они считаются особыми случаями.

Я почти всегда соблюдаю правила.Для меня есть четыре сценария — по сути, само правило и несколько исключений (все под влиянием Java):

  1. Может использоваться кем угодно за пределами текущего класса, доступ к нему осуществляется через геттеры/сеттеры.
  2. При использовании внутри класса обычно предшествует «this», чтобы было ясно, что это не параметр метода.
  3. Что-то, что должно было оставаться очень маленьким, например, транспортный объект — по сути, прямой набор атрибутов;все публичное
  4. Должен быть неприватным для какого-то расширения

Здесь есть практическая проблема, которая не решается большинством существующих ответов.Инкапсуляция и доступность чистых, безопасных интерфейсов к внешнему коду всегда хороши, но это гораздо важнее, когда код, который вы пишете, предназначен для использования большой в пространстве и/или времени «пользовательской» базой.Я имею в виду, что если вы планируете, чтобы кто-то (даже вы) поддерживали код в будущем, или если вы пишете модуль, который будет взаимодействовать с кодом более чем нескольких других разработчиков, вам нужно подумать гораздо больше. осторожно, чем если бы вы писали код, который либо разовый, либо полностью написанный вами.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top