РЕДАКТИРОВАТЬ: Я ответил на этот вопрос, потому что множество людей, изучающих программирование, задают этот вопрос, и большинство ответов очень технически компетентны, но их не так легко понять, если вы новичок. Мы все были новичками, поэтому я решил попробовать свои силы в более дружественном для новичков ответе.
Два основных из них - полиморфизм и проверка. Даже если это просто дурацкая структура данных.
Допустим, у нас есть этот простой класс:
public class Bottle {
public int amountOfWaterMl;
public int capacityMl;
}
Очень простой класс, показывающий, сколько жидкости в нем и какова его емкость (в миллилитрах).
Что происходит, когда я делаю:
Bottle bot = new Bottle();
bot.amountOfWaterMl = 1500;
bot.capacityMl = 1000;
Ну, вы же не ожидали, что это сработает, верно?
Вы хотите, чтобы была какая-то проверка на вменяемость. И что еще хуже, что, если я никогда не указывал максимальную мощность? О боже, у нас проблема.
Но есть еще одна проблема. Что, если бы бутылки были всего лишь одним типом тары? Что, если бы у нас было несколько контейнеров, все с емкостью и количеством заполненной жидкости? Если бы мы могли просто создать интерфейс, мы могли бы позволить остальной части нашей программы принять этот интерфейс, и бутылки, канистры и все такое прочее работали бы взаимозаменяемо. Разве это не было бы лучше? Поскольку интерфейсы требуют методов, это тоже хорошо.
В итоге мы получим что-то вроде:
public interface LiquidContainer {
public int getAmountMl();
public void setAmountMl(int amountMl);
public int getCapacityMl();
}
Отлично! А теперь просто изменим Bottle на это:
public class Bottle extends LiquidContainer {
private int capacityMl;
private int amountFilledMl;
public Bottle(int capacityMl, int amountFilledMl) {
this.capacityMl = capacityMl;
this.amountFilledMl = amountFilledMl;
checkNotOverFlow();
}
public int getAmountMl() {
return amountFilledMl;
}
public void setAmountMl(int amountMl) {
this.amountFilled = amountMl;
checkNotOverFlow();
}
public int getCapacityMl() {
return capacityMl;
}
private void checkNotOverFlow() {
if(amountOfWaterMl > capacityMl) {
throw new BottleOverflowException();
}
}
Я оставлю определение исключения BottleOverflowException в качестве упражнения для читателя.
Теперь обратите внимание, насколько это надежнее. Теперь мы можем работать с любым типом контейнера в нашем коде, приняв LiquidContainer вместо Bottle. И то, как эти бутылки справляются с подобными вещами, может быть разным. У вас могут быть бутылки, которые записывают свое состояние на диск при его изменении, или бутылки, которые сохраняются в базах данных SQL или GNU знает что еще.
И все они могут иметь разные способы справиться с различными воплями. Бутылка просто проверяет и, если она переполняется, выдает исключение RuntimeException. Но это может быть неправильным поступком.
(Есть полезное обсуждение обработки ошибок, но я намеренно сохраняю его очень простым. Люди в комментариях, скорее всего, укажут на недостатки этого упрощенного подхода.;))
И да, похоже, что мы переходим от очень простой идеи к быстрому получению более качественных ответов.
Обратите внимание, что вы не можете изменить вместимость бутылки. Теперь он высечен в камне. Вы можете сделать это с помощью int, объявив его final. Но если бы это был список, вы могли бы очистить его, добавить в него новые элементы и так далее. Вы не можете ограничить доступ прикосновением к внутренностям.
Есть еще третья вещь, о которой не все учли: геттеры и сеттеры используют вызовы методов. Это означает, что везде они выглядят как обычные методы. Вместо того, чтобы иметь странный специфический синтаксис для DTO и прочего, у вас везде одно и то же.
@Dean J: Дублируйте со многими другими вопросами: coderhelper.com/search?q=getters+setters
Конечно, и то, и другое одинаково плохо, когда объект не нуждается в изменении свойства. Я бы предпочел сделать все приватным, а затем добавить геттеры, если это полезно, и сеттеры, если необходимо.
Гугл "соучастники зла"
«Аксессоры - зло», если вы пишете функциональный код или неизменяемые объекты. Если вы пишете изменяемые объекты с сохранением состояния, они очень важны.
Скажи, не спрашивай. pragprog.com/articles/tell-dont-ask
Осмелюсь сказать, что за исключением 6 и 8 ни один из этих пунктов не применяется в 99% случаев. Тем не менее, я бы хотел, чтобы java использовала
String s = employee.name()
иemployee.name("oscar")
вместо getName () setName ()@Oscar Reyes, да, я согласен с тем, что 6 и 8 - самые распространенные два из моего опыта. Тем не менее, я по-прежнему предпочитаю не использовать синтаксис C # ish; мой старик мозг запутался.
Что ж, я удивлен, что никто не говорит о синхронизации данных. Предположим, вы используете
public String foo;
, это не потокобезопасно! Вместо этого вы можете определить элементыget
terset
с методами синхронизации, чтобы избежать неверности данных [я имею в виду, что другой поток испортил foo]. Я чувствовал, что об этом стоит упомянуть.@goldenparrot: Чаще, чем никогда, синхронизация одиночных обращений к объекту оказывается неэффективной или даже склонной к проблемам синхронизации (когда вам нужно изменить сразу несколько свойств объекта ). IME, вам часто нужно обернуть операции в целом, которые осуществляют множественный доступ к объекту. Этого можно добиться только путем синхронизации на стороне пользователей типа.
Не объединяйте ваши любимые части всех ответов в сам вопрос. Это сайт вопросов и ответов, а не форум.
@ErikReppen, что я должен прочитать, чтобы понять ООП в первую очередь?
Существует также очень важная проблема совместного использования кода с другими людьми: если вы рассматриваете поля объекта как части внешнего интерфейса для использования другими людьми, то изменение поля на функцию может оказаться невозможным без нарушения изменений в коде других людей.
@ErikReppen Спасибо за ссылку, я работаю над пониманием того, что должно быть ООП, но это оказывается трудным из-за количества распространяющейся дезинформации. P.S. Я также считаю, что Википедия - один из лучших источников во многих областях науки.
@TimoHuovinen - все дело в том, чтобы не выяснять, какая из 500 функций на самом деле изменяет значение. Вы делаете ценность или набор ценностей чем-то ответственным, а не чем-то, что нужно помещать на "американские горки" парам / возвратов. Избегайте ванильных геттеров / сеттеров и общедоступных свойств, и вам часто придется остановиться и подумать о том, как смоделировать свое приложение таким образом, чтобы его было легче читать, изменять, повторно использовать большие части, отлаживать и иметь уверенность. в без проверки bejeezus из него. Java и C # превзошли ООП, а затем отказались от него, ИМО.
Другая статья пытается дать рекомендации о том, где и когда использовать геттеры и сеттеры: Общедоступные или частные переменные-члены? Принцип YAGNI направляет нас не добавлять конструкции, пока мы действительно не узнаем, что они нам понадобятся.
Пункты 2, 3 (?), 4, 5 и 8: предварительно да. Остальные, однозначно нет. Почему имеет значение, использовали ли вы Python, C #, Ruby, Objective-C или esolang, если ваш код простой, элегантный и легко обслуживаемый / расширяемый?
666 голосов за не кажутся совпадением
«Люди могут легко сказать, что вы не использовали Python». на самом деле должно быть Nr. 1!
@Kaiserludi Не могли бы вы проиллюстрировать, как это хорошо?
@DeanJ Не могли бы вы объяснить номер 10?
@Joshua: Я просто шутил, потому что автор явно несерьезно добавлял его в список.
11. Это полезные места для установки точек останова при отладке состояния, полученного / измененного из различных мест.
Недавний вопрос напомнил мне еще об одной причине для set-методов: возможность инициировать изменение свойства .