Может ли переименование метода сохранить инкапсуляцию?

9

Я читал эту страницу о том, когда геттеры / сеттеры оправданы, и ОП дал следующий пример кода:

class Fridge
{
     int cheese;

     void set_cheese(int _cheese) { cheese = _cheese; }
     int get_cheese() { return cheese; }
 }

void go_shopping(Fridge fridge)
{
     fridge.set_cheese(fridge.get_cheese() + 5);        
}

В признанных ответ гласит:

Кстати, в вашем примере, я дал бы класс и методы, вместо и . Тогда у вас все равно будет инкапсуляция.FridgeputCheese()takeCheese()get_cheese()set_cheese()

Как сохраняется инкапсуляция, переименовывая ее из get / set в putCheese()/ takeCheese()Вы очевидно получаете / устанавливаете значение, так почему бы просто не оставить его как get / set?

В том же ответе также говорится:

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

В этом случае у нас есть только одна переменная, cheeseи вы можете захотеть взять и вернуть сыр в холодильник, поэтому пара get / set в этом случае оправдана.

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

Ответы:

17

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

public class Fridge
{
    private int numberOfCheeseSlices;

    public void AddCheeseSlices(int n)
    {
         if(n < 0) { 
             throw new Exception("you cant add negative cheese!");
         }
         if(numberOfCheeseSlices + n > this.capacityOfFridge) { 
             throw new Exception("TOO MUCH CHEESE!!");
         }
         ..etc
         this.numberOfCheeseSlices += n;
    }
}

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

Ewan
источник
2
Правильно, но вы все равно можете оставить все как setCheese()есть с той же логикой в ​​сеттере. В любом случае, вы пытаетесь скрыть тот факт, что вы используете сеттер, переименовывая метод, но вы явно что-то получаете / устанавливаете.
QueenSvetlana
21
@QueenSvetlana это не сеттер. Это операция. Вы говорите холодильнику «вот еще один сыр», и он добавляет его к внутреннему количеству сыпучих сыров. Сеттер сказал бы: «Теперь у тебя 5 сыров». Это функционально разные операции, а не просто имена.
Муравей Р
1
Вы могли бы назвать это setCheese, но это сбило бы с толку, поскольку это не установило бы стоимость сыра.
Эван
6
Обратите внимание, что здесь есть ошибка переполнения целых чисел. Если сыра уже больше 0 AddCheese(Integer.MAX_VALUE), произойдет сбой, но не будет.
user253751
3
это будет полностью описано в разделе ... и т.д.
Ewan
5

Получатели и установщики нарушают инкапсуляцию каждый раз по определению. Что может утверждать, что иногда мы должны сделать это. С этим из пути, вот мои ответы:

Как сохраняется инкапсуляция, переименовывая ее из get / set в putCheese () / takeCheese () Вы, очевидно, получаете / устанавливаете значение, так почему бы просто не оставить его как get / set?

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

Так getи setтехнические термины , и , кажется , не имеют ничего общего с «холодильником» области, в то время как putи , takeвозможно , на самом деле какой - то смысл.

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

В этом случае у нас есть только одна переменная, сыр, и вы можете захотеть взять и вернуть сыр в холодильник, поэтому пара get / set в этом случае оправдана.

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

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

Роберт Бройтигам
источник
10
Getters and setters break encapsulation every single time- Это слишком сильно сформулировано на мой вкус. Методы (включая геттеры и сеттеры) имеют ту же цель, что и ворота на концерте: они позволяют упорядоченно входить и выходить из зала.
Роберт Харви
8
«Получатели и установщики нарушают инкапсуляцию каждый раз по определению» - по какому определению? У меня тоже есть проблема с этим, потому что геттеры и сеттеры являются просто частью открытого интерфейса, как и любой другой публичный член; они только нарушают инкапсуляцию, если раскрывают детали реализации, которые считаются внутренними по замыслу (то есть по решению программиста (или команды)). Тот факт, что геттеры и сеттеры часто добавляются случайным образом, а контроль сцепления является запоздалой мыслью, - это совсем другой вопрос.
Филипп Милованович
2
@ FilipMilovanović Справедливая точка. Как я упоминаю в ответе, «инкапсуляция» используется для скрытия внутренних объектов объекта (== состояние объекта == переменные экземпляра). Геттеры и сеттеры публикуют внутренние объекты. Поэтому по этим определениям они находятся в постоянном конфликте. Теперь, независимо от того , иногда мы нужно сломать инкапсуляция другой вопрос, который должен быть обработан отдельно. Чтобы быть ясным, говоря, что сеттеры / геттеры всегда нарушают инкапсуляцию, я не говорю, что это всегда "неправильно", если это помогает.
Роберт
5
методы получения и установки не «нарушают инкапсуляцию буквально всегда». Методы получения и установки часто даже не ссылаются на члены ниже, выполняют какие-то операции или определены исключительно, у вас может быть только метод получения или может быть только установка. Он нарушает инкапсуляцию, когда методы получения и установки не делают ничего, кроме доступа к элементу, и оба определены для одних и тех же полей. Даже это может быть необходимо для сопровождающих библиотек, которые не хотят ломать ABI / API внутренними изменениями и избегать перекомпиляции клиента.
WHN
4
Хорошо, геттеры и сеттеры только в 99% случаев нарушают инкапсуляцию. Счастливый? И, выставляя тип инкапсулированного объекта, они определенно нарушают инкапсуляцию. Если у вас есть public int getFoo()практически, практически невозможно перейти на другой тип.
user949300
3

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

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

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

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

В этом конкретном случае явно необходимо изменить стоимость сыра в приложении. Независимо от того, как это делается, через get / set или add / remove, при условии, что методы инкапсулированы в классе, вы следуете объектно-ориентированному стилю.

Для пояснения я приведу пример того, как нарушается инкапсуляция, предоставляя доступ независимо от имени метода или логического выполнения.

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

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

Как и большинство вещей, определение инкапсуляции не буквальное, а относительное. Должны ли вы видеть Х вне класса? Должны ли вы быть в состоянии изменить Y? Все ли это относится к вашему объекту здесь, в этом классе, или функциональность распространяется на несколько классов?

user343330
источник
Давайте посмотрим на это прагматично. Вы говорите, что инкапсуляция не нарушается, если код предназначен таким образом, или если есть «законная» причина. По сути, это означает, что мы никогда не можем сказать, что инкапсуляция нарушена, потому что разработчик должен только сказать, что она «предназначена» таким образом, или была «законная» причина. Так что это определение в принципе бесполезно, не так ли?
Роберт
Нет, я говорю, что инкапсуляция не нарушается просто предоставлением доступа. И это не имеет ничего общего с тем, что говорит программист, а с тем, каковы потребности приложения. Приложению необходимо иметь доступ для управления объектами, инкапсуляция - это управление доступом. Приложению может потребоваться «установить» атрибут объекта, но логика и / или вычисления, необходимые для выполнения этой «установки», должны быть заключены в класс объекта.
user343330
Я думаю, что в этом мы коренным образом отличаемся, вы говорите: «Приложению может потребоваться« установить »атрибут объекта ...». Я так не думаю. Выбор дизайна, который включает в себя установку или получение атрибута, остается за разработчиком. Это выбор! Существует много способов кодирования чего-либо, но не все из них включают получение или установку значений переменных экземпляра.
Роберт
Может быть ... Обычно дизайн основан на удовлетворении потребностей, независимо от того, выбран ли программист, исходя из бизнес-среды. В любом случае, если у приложения есть фундаментальная потребность манипулировать значением, которое является частью объекта, вы должны каким-то образом сделать эту функцию доступной. Предоставление точки входа для работы не нарушает инкапсуляцию. Возьмите приложение для продаж. В какой-то момент ваша транзакция должна рассчитать налог, сделав это в объекте транзакции, чтобы он был инкапсулирован, но приложение должно иметь возможность указать транзакцию.
CalcTax
Сеттеры являются плохими примерами из-за своей простоты, если рассматривать их в терминах функции, которая выполняет гораздо больше работы, в результате чего атрибут объекта обновляется по сравнению с чем-то вне класса, выполняющим работу, а затем говоря, object.attribute = x. Первый - это хорошая инкапсуляция при обеспечении соответствующего доступа, второй - нет. Что касается геттеров, должно ли приложение знать только этот фрагмент объекта, чтобы делать что-то еще, что не может содержаться в классе объекта? если да, разоблачение этого не нарушает вашу инкапсуляцию. если нет, то инкапсулируйте его в класс объекта
user343330
1

Это не просто переименование метода. Два метода работают по-разному.

(Представьте себе это в уме)

get_cheese и set_cheese выставляют сыр. putCheese () и takeCheese () скрывают сыр, заботятся об управлении им и дают пользователю возможность обрабатывать его. Наблюдатель не видит сыр, он видит только два метода манипулирования им.

Daneesha
источник