Luźna definicja: singleton to obiekt,
który posiada tylko jedną instancję w aplikacji. Zwykle stosowany
jaki kontener dla konfiguracji. Dlaczego jedna instancja? Ano
dlatego, by spróbować uniknąć niespójności związanych z tym,
że różne obiekty posiadają nieaktualne informacje dot. wartości
w nim przechowywanych np. poprzez przekazywanie obiektu konfiguracji przez parametr. Dodatkowy problem stanowią próby
aktualizacji wartości Singletona tak, by przy odczycie w innym
miejscu programu dostać je spójne, a nie zaktualizowane częściowo.
Pusty Singleton w Java:
public
enum MySingleton {
INSTANCE;
}
W Clojure wartości obiektów można
zmieniać tylko w trnsakcji. Najprostszą konstrukcją zapewniającą
funkcjonalność Singletona jest atom.
Pusty atom w Clojure:
(def
MySingleton (atom nil))
Załóżmy, że posiadamy klasę
konfiguracji, która przechowuje dane dot. logowania do bazy danych:
public
class LoginData {
private
String name;
private
String password;
public
LoginData(String name, String password) {
this.name
= name;
this.password
= password;
}
public
String getName() {
return
name;
}
public
String getPassword() {
return
password;
}
public
void setName(String name) {
this.name
= name;
}
public
void setPassword(String password) {
this.password
= password;
}
@Override
public
String toString(){
StringBuilder
sb = new StringBuilder();
sb.append("Name:
").append(name).append(", Password: ")
.append(password);
return
sb.toString();
}
}
Singleton Java będzie wyglądać
tak:
public
enum MyConfig {
INSTANCE;
private
final LoginData ld = new LoginData("myname", "mypassword");
public
LoginData getLoginData() {
return
ld;
}
}
W Clojure tworzymy następująco
interfejs obiektu konfiguracji:
(defprotocol
LoginProtocol
"Login
protocol"
(toStr
[this] "to String"))
Obiekt,
który przechowa dane (odpowiednik klasy, dziedziczący po
nadrzędnym):
(defrecord
LoginData [name password]
LoginProtocol
(toStr
[this] (str "Name: " (:name this) ", Password: "
(:password this))))
Atom,
który posłuży jako singleton z obiektu LoginData
(def
myconfig (atom (LoginData. "myname" "mypassword")))
Dostęp
do pól singletona, przykład:
(:name
@myconfig)
Aktualizacja
danych w transakcji:
(swap!
myconfig #(assoc % :name "newname" :password
"newpassword"))
Zamiana
na łańcuch znakowy i wypisanie w konsoli:
(println
(toStr @myconfig))
Wyjaśnienia
Obiekt
LoginData jest obiektem tylko do odczytu. Pod spodem to zwykła mapa
klucz-wartość : {:name
"newname", :password "newpassword"}.
Można utowrzyć inny obiekt i podmienić całość, ale nie można
bezpośrednio modyfikować pól mapy. Stąd dodatkowy kontener typu
atom. Atom w Clojure
modyfikuje się poprzez wysłanie do niego funkcji, która przyjmuje
starą jego zawartość jako parametr. Tutaj uzyłem funkcji anonimowej
#(), gdzie parametr oznaczony jest jako %. Podmiana jest
natychmiastowa i niezauważalna dla reszty programu. Znaczek @ to
skrót syntaktyczny od funkcji deref, która przekazuje wartość
atomu.
Przydatne
linki:
Brak komentarzy:
Prześlij komentarz