EDYCJA: Odpowiedziałem na to pytanie, ponieważ wiele osób uczy się programowania, a większość odpowiedzi jest bardzo kompetentna technicznie, ale nie jest tak łatwo zrozumieć, jeśli jesteś początkującym. Wszyscy byliśmy początkującymi, więc pomyślałem, że spróbuję swoich sił, by uzyskać bardziej przyjazną odpowiedź dla początkujących.
Dwa główne to polimorfizm i walidacja. Nawet jeśli to tylko głupia struktura danych.
Powiedzmy, że mamy tę prostą klasę:
public class Bottle {
public int amountOfWaterMl;
public int capacityMl;
}
Bardzo prosta klasa, która przechowuje ilość płynu i jaka jest jego pojemność (w mililitrach).
Co się stanie, gdy to zrobię:
Bottle bot = new Bottle();
bot.amountOfWaterMl = 1500;
bot.capacityMl = 1000;
Nie spodziewałbyś się, że to zadziała, prawda? Chcesz, żeby była jakaś kontrola zdrowia psychicznego. Co gorsza, co jeśli nigdy nie podałem maksymalnej pojemności? Och, kochanie, mamy problem.
Ale jest też inny problem. Co jeśli butelki byłyby tylko jednym rodzajem pojemnika? Co gdybyśmy mieli kilka pojemników, wszystkie o pojemnościach i ilościach napełnionych płynem? Gdybyśmy tylko mogli stworzyć interfejs, moglibyśmy pozwolić, aby reszta naszego programu zaakceptowała ten interfejs, a butelki, jerrycany i wszelkiego rodzaju rzeczy po prostu działałyby zamiennie. Czy nie byłoby lepiej? Ponieważ interfejsy wymagają metod, jest to również dobra rzecz.
Skończylibyśmy z czymś takim jak:
public interface LiquidContainer {
public int getAmountMl();
public void setAmountMl(int amountMl);
public int getCapacityMl();
}
Świetny! A teraz zmieniamy Butelkę na to:
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();
}
}
Pozostawię definicję BottleOverflowException jako ćwiczenie czytelnikowi.
Teraz zauważ, o ile bardziej jest to solidne. Możemy teraz obsługiwać dowolny rodzaj pojemnika w naszym kodzie, akceptując LiquidContainer zamiast Bottle. A jak te butelki radzą sobie z tego rodzaju rzeczami, wszystko może się różnić. Możesz mieć butelki, które zapisują swój stan na dysk, gdy się zmienia, lub butelki, które oszczędzają w bazach danych SQL lub GNU wiedzą co jeszcze.
A wszystko to może mieć różne sposoby radzenia sobie z różnymi sekcjami. Butelka po prostu sprawdza, a jeśli jest przepełniona, zgłasza wyjątek RuntimeException. Ale to może być niewłaściwa rzecz. (Istnieje użyteczna dyskusja na temat obsługi błędów, ale celowo utrzymuję ją bardzo prosto. Ludzie w komentarzach prawdopodobnie zwrócą uwagę na wady tego uproszczonego podejścia;))
I tak, wydaje się, że przechodzimy od bardzo prostego pomysłu do szybkiego uzyskiwania znacznie lepszych odpowiedzi.
Należy również pamiętać, że nie można zmienić pojemności butelki. Teraz jest osadzony w kamieniu. Możesz to zrobić z int, deklarując to jako ostateczne. Ale jeśli to była lista, możesz ją opróżnić, dodać do niej nowe rzeczy i tak dalej. Nie możesz ograniczyć dostępu do dotykania wnętrzności.
Jest też trzecia rzecz, do której nie wszyscy się zwracali: pobierający i ustawiający używają wywołań metod Oznacza to, że wszędzie wyglądają jak zwykłe metody. Zamiast mieć dziwną specyficzną składnię dla DTO i innych rzeczy, wszędzie masz tę samą rzecz.