Vaadin 7 i Scala po raz trzeci
W poprzednich wpisach poświęconych Vaadin 7 i Scali pokazałem jak można uprościć sobie życie i klepanie kodu Vaadin za pomocą Scali. Przy czym starałem się by prezentowany kod był „javopodobny” względnie nie wykorzystywał tych elementów języka Scala, które w znaczący sposób odróżniają go od czystej Javy. Dziś zaprezentuję najbardziej scalowe podejście do rozwiązania problemu rozwlekłego API Vaadin za pomocą Scali. Jak słusznie zauważył Miki zamiast wrappera można użyć mechanizmu typów domniemanych (ang. implicit conversion).
Wprowadzenie krótkie
Java jest językiem silnie typowanym tzn. że jeżeli zadeklarujemy iż dana zmienna jest typu Samochód to chciał nie chciał musimy używać tylko obiektów, które są samochodami w odniesieniu do tej zmiennej. Możemy przypisać do niej co prawda coś co samochód rozszerza, ale i tak będziemy mieli do dyspozycji tylko metody z klasy Samochód. Na drugim końcu są języki słabo typowane takie jak JavaScript, php, Ruby. W ich przypadku interpreter stara się wywnioskować „co poeta miał na myśli”. Z jednej strony jest to wygodne ponieważ zwalnia programistę z konieczności konwersji typów, co jest szczególnie widoczne w przypadku konwersji pomiędzy typami podstawowymi (czytaj tymi bez których ciężko się obyć) np. Integer String czy String Date. Jednak z drugiej strony może to doprowadzić do niezbyt przyjemnych sytuacji kiedy interpreter chce być „mondrzejszy niż ustawa przewiduje” oraz wymusza zachowanie pewnych konwencji w notacji.
Scala idzie na pewien kompromis wprowadzając wyżej wspomniany mechanizm domniemania typu. W ogólności zasada jest prosta. Definiujemy sobie metodę, która przyjmuje jako parametr obiekty typu A i zwraca obiekty typu B. W trakcie kompilacji gdy kompilator trafia na wywołanie metody przyjmującej jako parametry obiekty typu B do której podano jako parametr obiekt typu A zaczyna sobie przeszukiwać świat w poszukiwaniu odpowiedniego konwertera. Jeżeli go trafi to podstawia odpowiednie wywołania. Jak nie trafi to no cóż… błąd kompilacji też błąd.
Praktyka
Na początek utwórzmy sobie obiekt, który będzie zawierał nasze konwertery. Będzie to object, bo nie potrzebujemy tu wielu instancji ani tym bardziej stanowości.
Listing 1. Obiekt MyConverters
object MyConverters {
implicit def toClick(m: => Unit) = new Button.ClickListener {
def buttonClick(p1: ClickEvent) {
m
}
}
}
Nazewnictwo jak widać nie jest wyszukane, ale nie w tym rzecz. Choć warto by w rzeczywistości używać nazw znaczących ponieważ mechanizm kompilatora trafiając na dwie konwersje pomiędzy tymi samymi typami powie delikatnie, że ma ambiwalentne uczucia co do naszej inteligencji (wprost „patrz co robisz debilu”).
Mając już przygotowany mechanizm konwertera warto go sobie zaaplikować:
Listing 2. Klasa MyVaadinUI
import com.vaadin.server.VaadinRequest
import com.vaadin.ui._
import MyConverters._
class MyVaadinUI extends UI {
var liczby: List[Int] = List(0)
override def init(request: VaadinRequest) {
val layout = new VerticalLayout()
layout.setMargin(true)
setContent(layout)
val liczbyLabel = new Label("0")
val input: TextField = new TextField("Podaj liczbę")
input.setValue("0")
val kolejnaBtn: Button = new Button("Kolejna")
input.setImmediate(true)
def y: Unit = {
liczby ::= input.getValue.toInt
liczbyLabel setValue liczbyLabel.getValue + "+" + input.getValue
input.setValue("")
return input.focus()
}
kolejnaBtn.addClickListener(y)
val sumaBtn = new Button("Suma")
def x: Unit = {
// sumowanie liczb
val wynik: Label = new Label(liczby.foldLeft(0) {
(a, b) => a + b
}.toString)
layout.addComponent(wynik)
liczbyLabel.setValue("0")
liczby = List(0)
}
sumaBtn.addClickListener(x)
layout.addComponent(input)
layout.addComponent(kolejnaBtn)
layout.addComponent(liczbyLabel)
layout.addComponent(sumaBtn)
}
}
I w ten oto prosty i przyjemny sposób mamy elegancki kod, w którym nie ma rozwlekłych nazw czy jawnego użycia wrapperów.