Java: Lambda výrazy a GUI, implementace návrhového vzoru Observer
Přiznám se na rovinu, kolem lambda výrazů coby novinky zavedené v Java 8 jsem dlouho chodil jako kolem horké kaše. Není divu, když jako první přiklad použití bývá uváděno tohle:
Collection.forEach( e -> e.doSomething() );
Proč další způsob, jak napsat smyčku přes prvky kolekce, když mohu použít již existující cyklus foreach? Po delším studování a experimentování se mi ale podařilo přijít na způsob, jak lambda výrazy efektivně využívat. Podívejme se, jak dobře využité lambda výrazy usnadní implementaci GUI.
Mám-li definovat stěžejní vlastnost dobrého programátora, pak je to cit pro zdroják. Technologie se vždycky dají naučit, ale encyklopedické znalosti z vás dobrého programátora neudělají. To z vás udělá až cit pro zdroják: vrozený talent tříbený praxí, jak technologie efektivně využít, aby program správně a efektivně pracoval a aby zdroják byl snadno čitelný a opakovaně použitelný.
Z pozice citu pro zdroják bych správné použití lambda výrazů definoval pravidlem: lambda pokud možno jednořádková. Výsledný zdroják je pak skutečně jednodušší a čitelnější.
Jako příklad z praxe uvedu situaci v SWT, kdy operace spuštěná v separátním vlákně potřebuje zobrazit výsledek v GUI (přičemž ke GUI může přistupovat pouze GUI vlákno):
myWindow.getDisplay().asyncExec( new Runnable() {
public void run() {
doSomething();
}
} );
Použitím lambda výrazu lze zdroják zkrátit na jeden řádek:
myWindow.getDisplay().asyncExec( () -> doSomething() );
Kratší, přehlednější, čitelnější.
Pozorovaný (subject)
Podíváme-li se na jakýkoli framework pro tvorbu GUI, zjistíme, že reakce na události ze strany uživatele jsou implementovány jako návrhový vzor Observer. Vizuální komponenty hrají roli pozorovaného, přiřazený pozorovatel implementuje vlastní funkcionalitu vyvolanou změnou stavu komponenty. Například tlačítko má pro událost "stisknuto" zaregistrováno jeden nebo více listenerů, které zajistí spuštění příslušné funkce.
Pokud definujeme vlastní vizuální komponentu (třeba i panel s více komponentami) a chceme, aby zbytek GUI reagovalo na změny v ní, můžeme ji jako pozorovaného implementovat takto:
import java.util.LinkedList;
import java.util.List;
public class MyComponent {
private List<MyListener> listeners = new LinkedList<>();
public void addListener( MyListener listener ) {
if( listener != null ) {
listeners.add( listener );
}
}
private void notifyListeners( newStatus ) {
listeners.forEach( l -> l.statusChanged( newStatus ) );
}
}
Metodu Collection.forEach()
jsem sice na počátku podrobil kritice, při implementaci reakce na událost je však její použití zcela na místě. Metodu addListener(...)
pro přidávání listenerů však musíme implementovat defenzivně a přidávaný listener testovat na nenullovost. Testování, jestli daný listener již nebyl jednou přidán do seznamu, není za normálních okolností nutné, hodí se však, pokud potřebujeme implementovat i odebírání listenerů.
Datový typ implementující stav v tomto příkladu neřeším.
Pozorovatel (observer)
Podívejme se nyní na implementaci pozorovatele.
@FunctionalInterface
public interface MyListener {
public void statusChanged( newStatus );
}
Za sebe doporučuji základ pro pozorovatele implementovat vždy jako interface, přestože definujeme komponentu pro jednorázové použití. Komponentu pak můžeme bez dalších úprav použít i v jiném kontextu, pokud takový požadavek nastane. Navíc při implementaci konkrétního pozorovatele můžeme s výhodou využít lambda výraz:
private void createContents() {
...
MyComponent myComponent = new MyComponent();
myComponent.addListener( newStatus -> doSomething( newStatus ) );
...
}
Jednoduchá implementace, kterou můžeme s výhodou použít, pokud komponenta definuje pouze jeden typ události. Pokud komponenta generuje více typů událostí, doporučuji spíše implementaci postaru, tj. aby interface definoval metody pro všechny typy událostí a konkrétního pozorovatele implementovat jako inner class.
Závěr
Ukázali jsme si základní princip, jak při implementaci GUI s výhodou využít lambda výrazy, aby výsledný zdroják byl jednodušší a přehlednější. Připravuji ale další články, kde si ukážeme, jak lambda výrazy využít ještě lépe.
Tagy: Java, Programování