"The Grid. A digital frontier. I tried to picture clusters of information as they move through the computer. What did they look like? Ships? Motorcycles? Were the circuits like freeways? I kept dreaming of a world I thought I’d never see. And then, one day, I got in." — Tron: Legacy

2014-07-06

Java vs Clojure : Singleton

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