РЕДАКТИРОВАТЬ: Я ответил на этот вопрос, потому что есть множество людей, изучающих программирование, спрашивающих об этом, и большинство ответов очень технически компетентны, но их не так легко понять, если вы новичок. Мы все были новичками, поэтому я решил попробовать свои силы в более дружелюбном ответе новичка.
Двумя основными из них являются полиморфизм и валидация. Даже если это просто глупая структура данных.
Допустим, у нас есть этот простой класс:
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();
}
Большой! И теперь мы просто изменим бутылку на это:
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, объявив его окончательным. Но если бы это был список, вы могли бы очистить его, добавить в него новые вещи и так далее. Вы не можете ограничить доступ к прикосновению к внутренностям.
Также есть третья вещь, к которой обращаются не все: геттеры и сеттеры используют вызовы методов. Это означает, что они везде выглядят как обычные методы. Вместо того, чтобы иметь странный специфический синтаксис для DTO и прочего, у вас везде одно и то же.