Zazwyczaj, żeby uchronić wewnętrzne pola danej klasy przed przypadkowymi zmianami,
stosuje się gettery/settery. Getter/setter (nowe polskie słowa ;-) to para publicznych metod pobierających i ustawiających prywatne pola klasy. Oto klasyczny przykład klasy z getterem getValue i setterem setValue.
public class Foo {
private int value = 666;
public void setValue(int newValue) { value = newValue; }
public int getValue() { return value; }
}
...
Foo foo = new Foo();
foo.setValue(100);
System.out.println("Foo value: " + foo.getValue());
...
Powyższe rozwiązanie ma jedną podstawową wadę - trzeba pisać dużo prawie identycznego kodu. Ponieważ jestem osobą leniwą, więc napisałem sobie klasę Property. Nie jest to może odkrycie Ameryki, ale przynajmniej rozwiązuje część problemów natury lenistwa. Poniżej znajduje się zmodyfikowana wersja poprzedniego kodu. Tym razem z wykorzystaniem klasy Property. Od razu widać, że zaoszczędziłem w ten sposób całe dwie linijki kodu. Hurra! Pole value jest zadeklarowane jako public final więc jest dostępne dla innych klas, i zarazem nie możemy zmieniać jego wartości.
public class Foo {
public final Property<Integer> value = new Property<Integer>(666);
}
...
Foo foo = new Foo();
foo.value.set(100); // ok
// foo.value = null; // błąd kompilacji, ponieważ foo.value jest final
System.out.println("Foo value is " + foo.value);
...
Przykład uproszczonej implementacji klasy Property oraz BooleanProperty:
public class Property<T> {
protected T value;
public Property() { this(null); }
public Property(T value) { this.value = value; }
public T get() { return value; }
public void set(T value) { this.value = value; }
public boolean isNull() { return value == null; }
@Override
public String toString() { return (value == null) ? null : value.toString(); }
}
public class BooleanProperty extends Property<Boolean> {
public BooleanProperty() { this(false); }
public BooleanProperty(boolean value) { super(value); }
public void no() { value = false; }
public void set(String value) { super.set(Boolean.parseBoolean(value)); }
public void toggle() { value = !value; }
public void yes() { value = true; }
}
Zauważ, że mając pole typu BooleanProperty, możesz napisać if (foo.value.get()) { ...
Takie rozwiązanie ma również niewielki minusy. Oprócz zmiennej typu Property<T>, w pamięci przechowywane jest również wewnętrzne pole value typu T, więc w niektórych przypadkach zużycie pamięci może wzrosnąć (jednak z drugiej strony oszczędzamy pamięć na metodach get/set, które są wspólne dla wszystkich pól typu Property<T>).
W niektórych przypadkach może nastąpić niewielka utrata przejrzystości kodu (np. addressBook.get().person.get().name.get()) i powstaje kandydat do The Daily WTF ;-)
Proszę o komentarz
Łał, mówisz o Javie jakby była czymś superłatwym... Z jakiej książki najlepiej zacząć naukę żeby przynajmniej zrozumieć o czym piszesz :)?
Jakoś nie rozumiem idei getterów i setterów skoro sprowadzają się do value = dupa i return value.
<flame>
Ech, toż to nawet JavaScript ma normalne gettery i settery:
function Foo(value) {
var _value = parseInt(value);
this.__defineGetter__("value", function() {
return _value;
});
this.__defineSetter__("value", function(newVal) {
_value = parseInt(newVal);
});
}
aFoo = new Foo(8);
alert(aFoo.value);
aFoo.value="2005r.";
alert(aFoo.value);
bFoo = new Foo(9);
alert(bFoo.value);
bFoo.value="50 procent Polaków nie wie, że stanowi połowę społeczeństwa";
alert(bFoo.value);
...a w Javie trzeba cudować. ;-)
</flame>
@zenon: Ja akurat nie uczyłem się z książek :) Wg mnie najlepiej wyznaczyć sobie jakiś z pozoru nieosiągalny cel (np. napisanie menedżera plików), a potem stopniowo tworzyć program krok-po-kroku (najpierw okno, tytuł okna, lista z plikami, itd.) Oczywiście jakieś dobre podstawy by się przydały. W archiwum grup dyskusyjnych temat książek był poruszany miliardy razy ;)
@zdzichu: ?ettery często wykonują bardziej złożone operacje niż tylko proste przypisanie. Poza tym zmniejsza się ryzyko, że przypadkowo przypiszesz do value jakąś przypadkową wartość.
@marcoos: Fajne, ale chyba parseInt ucina tekst za liczbą ;)
Jak zmniejszaja ryzyko? Czym się różni foo.value = 0xbad od foo.setValue(0xbad) ?
@zdzichu:
1. Po 12 godzinach pisania foo.value=0xdeadbeef można pomylić z value=0xdeadbeef (lub odwrotnie).
2. Funkcja foo.setValue(0xdeadbeef) może wywołać wyjątek, jeśli wartość jest nieprawidłowa.
3. ?ettery są też używane przez JavaBeans (akurat nie znam się na tym)
Jam: parseInt() jest tam tylko po to, żeby w skutkach się to wyraźnie różniło od foo.value = newValue (kiedy value nie jest setterem/getterem).
a jak takie podejscie sie sprawuje gdy sie z refleksji kozysta ?
jak na to by zareagowal taki kontener serwletow czy inszy hibernate ?
Założę się, że ktoś już napisał odpowiednie adnotacje do generowania ?etterów.
@Jezuch: generowanie na podst. adnotacji a zwykła klasa, to dwa różne podejścia
mbnmcbn
?ettery są bardzo przydatne. Dzięki nim np. na pierwszy rzut oka widać, co wolno zrobić z daną klasą, a czego nie wolno. Otwieranie dostępu „pól klasy” (cytuję, nie znam javy) zwiększa co prawda „uniwersalność” takiej klasy, bo można z zewnątrz w te pola wpisać co nam się rzewnie podoba, ale klasa(instancja) nie ma wtedy kontroli nad poprawnością tych danych. Generalnie przy większym projekcie robi się wtedy chaos, chaos powstaje zawsze, gdy nie ma z góry określonych metod postępowania, a IMVHO ?ettery działają narzucają takie reguły :-)
Ale ze mnie PA-TA-FIAN.