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

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

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

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

Ответ 1

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

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

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

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

Ответ 2

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

Ответ 3

Да, вопрос инкапсуляции. Выявление основной реализации делает (по крайней мере) две вещи неправильными:

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

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

Ответ 4

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

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

Ответ 5

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

Ответ 6

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

Пример: Я пишу код, который обновляет переменную, и мне нужно обязательно убедиться, что изменения Gui отражают изменения, самый простой способ - добавить метод доступа (aka a "Setter" ), который вызывается вместо обновления данных, обновляется.

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

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

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

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

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

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

Ответ 7

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

Ответ 8

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

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

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


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

Ответ 9

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

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

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

Немного трудно оправдать.

Ответ 10

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

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

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

Ответ 11

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

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

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

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

Ответ 12

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

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

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

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

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

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

Даже стандартные библиотеки java имеют некоторые случаи общедоступных полей.

Ответ 13

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

Например: Person.Hand.Grab(howquick, howmuch);

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

Ответ 14

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

Однако для меня "инкапсуляция" есть:

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

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

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

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

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

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

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

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

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

Ответ 15

@VonC

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

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

Привет,

Под ред.

Ответ 16

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

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

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

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

В одном проекте есть модель GUI, которая имеет 15 геттеров и сеттеров, из которых только три метода получения тривиальны (такие, что 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();
}

Ответ 17

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

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

Ответ 18

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

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

Ответ 19

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

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

Ответ 20

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

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

Ответ 21

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

  • Используется чем-либо вне текущего класса, доступ к которому осуществляется через getters/seters
  • Обычно для использования внутри класса используется 'this', чтобы он дал понять, что это не параметр метода
  • Что-то означало оставаться очень маленьким, как транспортный объект - в основном прямой снимок атрибутов; все публичные
  • Нужно быть не-частным для расширения какого-то типа

Ответ 22

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

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