Nowe wideo o lambdach i testowaniu

December 8th, 2014

Zapraszam do obejrzenia

Powinno już być lepiej z dźwiękiem i obrazem :)

Gdzie Diabeł nie może tam Wrapper pośle

December 1st, 2014

All problems in computer science can be solved by another level of indirection

– David Wheeler

I tego się trzymajmy. Jednak nie byłbym sobą, gdybym nie pokazał zastosowania tej reguły na przykłądzie z życia wziętym.

Problem

Mamy niezmienialną (w sensie – nie możemy zmienić kodu) klasę reprezentującą jakąś informację. W naszym przypadku była to encja reprezentująca naruszenie spójności dla instrumentu finansowego. Otrzymywaliśmy listę tego typu encji, a naszym zadaniem było jej przefiltrowanie pod kątem unikalności wpisów. Wpis był unikalny, czyli encje były równe, jak ich atrybuty były równe. Rzecz w tym, że hashCode i equlas nie były zaimplementowane więc encje zawsze były różne niezależnie od tego co miały w bebechach. Wykluczało to zastosowanie najprostszej metody na redukcję za pomocą Seta.

Listing 1. Redukcja za pomocą Set

public Collection<MyEntity> reduceToUnique(Collection<MyEntity> notUnique){
   return new HashSet<MyEntity>(notUnique);
}

Jak by sobie z tym można było poradzić…

Wprowadzamy Wrapper

Uwaga, kod już jest generyczny. W moim przypadku pierwsza wersja była niegeneryczna i przygotowana pod konkretna encję. Omówię ją w dużym skrócie, ale jest ona przykładem implementacji “starupowej” – byle jak byle działało.

By rozwiązać ten problem wprowadziłem dodatkową warstwę pomiędzy obiektami otrzymywanymi z zewnątrz, a naszą logiką. Jego zadanie polega na dostarczeniu naszej własnej implementacji dla metod hashCode i equlas. W tym miejscu natknąłem się na pewien problem w implementacji, który spowodował, że pierwsze, niegeneryczne, podejście do implementacji choć zakończyło się sukcesem to zdecydowałem się na powtórą implementację całego rozwiązania.

W takim niegenerycznym przypadku tworzymy jedeną klasę wrappera na każdą klasę, którą chcemy obudować. Powoduje to, że pojawia się masa zduplikowanego kodu. Przykładowo dla kasy Person z biblioteki JFairy, która to klasa zachowuje się jak nasza encja, będziemy mieli:

Listing 2. Niegeneryczny Wrapper

class PersonWrapper{
    private final Person person;

    public PersonWrapper(Person person) {
        this.person = person;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof PersonWrapper)) {
            return false;
        }

        PersonWrapper that = (PersonWrapper) o;

        if (person != null ? !person.firstName().equals(that.person.firstName()) : that.person != null) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        return person != null ? person.firstName().hashCode() : 0;
    }
}

Dodatkowo jeżeli chcemy zmienić implementację warunków to musimy stworzyć nową klasę. Czyli jest źle. Znaczy się jeżeli chcemy to zrobić punktowo dla jakiejś jednej klasy i zestawu warunków to będzie ok. Jednak jeżeli będziemy tego używać w wielu miejscach to warto zaimplementować to porządnie.

Jak widać w niegenerycznej wersji problemem było sztywne zaszycie w kodzie klasy zarunków równości. By wyciągnąć je na zewnątrz będziemy potrzebować dwóch interfejsów. Po jednym na każdą metodę. Dlaczego dwóch? Ponieważ dzięki temu możemy zaimplementować je później jako lambdy.

Listing 3. Generyczny Wrapper i dodatkowe interfejsy

class Wrapper<T> {

    public final T bean;
    private final Eq<T> eq;
    private final Hc<T> hc;

    Wrapper(T bean, Eq<T> eq, Hc<T> hc) {
        this.bean = bean;
        this.eq = eq;
        this.hc = hc;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof Wrapper)) {
            return false;
        }

        Wrapper wrapper = (Wrapper) o;

        Object right = wrapper.bean;
        if (right != null && right.getClass().isAssignableFrom(bean.getClass()))
            return eq.eq(this.bean, (T) right);

        return false;
    }

    @Override
    public int hashCode() {
        return bean != null ? hc.hashCode(bean) : 0;
    }

}
interface Eq<T> {

    boolean eq(T left, T right);
}

interface Hc<T> {

    int hashCode(T t);
}

Jak łatwo zauważyć zarówno hashCode jak i equals zostały zaimplementowane w ten sposób by po dokonaniu wstępnej weryfikacji obudowanego obiektu oddelegować konkretne wyliczenia do implementacji interfejsów.

Czas na przykłado wykorzystania. W tym celu stworzymy losową listę obiektów Person i wyciągniemy unikalny zestaw. Warunkiem równości będzie płeć (jest to warunek w miarę rozsądny, bo zbiór wartości jest ograniczony, a możliwość wylosowania tylko osób o takiej samej płci przy dużej liczbie losowań jest znikoma).

Listing 4. Program przykładowy

public class WrapperExample {

    public static void main(String[] args) {
        Fairy fairy = Fairy.create(Locale.forLanguageTag("PL"));
        List<Person> persons = IntStream.range(0, 1000)
                .collect(ArrayList::new, (c, i) -> c.add(i), ArrayList::addAll)
                .stream()
                .map(i -> fairy.person())
                .collect(Collectors.toList());
        System.out.println("Raw data " + persons.size());

        Set<Person> personSet = persons.stream().collect(Collectors.toSet());
        System.out.println("Set default " + personSet.size());

        List<Person> personWrappersSex = persons.stream()
                .map(p -> new Wrapper<Person>(p,
                        (left, right) -> (left.isMale() && right.isMale()) || (left.isFemale() && right.isFemale()),
                        g -> g.isMale() ? 1 : 0
        )).collect(Collectors.toSet()).stream()
                .map(w -> w.bean).collect(Collectors.toList());
        System.out.println("Set from wrapper sex " + personWrappersSex.size());
    }

}

Najpierw tworzymy listę tysiąca osób. Następnie próbujemy wrzucić je do Seta i otrzymujemy zbiór o tej samej wielkości. W kolejnym kroku najpierw mapujemy wszystkie obiekty na Wrapper z odpowiednimi warunkami równości. Następnie zbieramy to do Seta, co powoduje wywołanie logiki z interfejsów Eq i Hc po czym znowu mapujemy wyłuskując Person i zbieramy do listy.

Podsumowanie

Jak widać po wprowadzeniu dodatkowej warstwy problem stał się trywialny i ograniczył się do napisania implementacji dla odpowiednich metod.

Nasza oryginalna implementacja jest jeszcze troche inna ponieważ mamy Javę 1.6 i Guavę co powoduje, że kod jest bardziej rozwlekły w wykorzystaniu.

Procrastination short story

November 25th, 2014

For people who wouldn’t like to read long posts.

Why?

1. Because all articles how to beat procrastination are to long for people who would do this.
2. Because I know what I write. Procrastination is my personal daemon everyday for years.

How?

1. Define “The Big Thing” that you want to achieve. It is to big to one bite. For me is to buy this toy.
2. Define milestones and write it on paper. I have my pr0-note. Don’t write it on computer. Paper is real and nad you can touch it. Computer note is abstract. You can’t create “mental link” with that abstract thing.
3. Milestones are to big, still. But they are easier to achieve, easier to manage and less abstract. My milestones e.g.:
– Save first 3K
– Save first 10k
– Save 30K
– Find source of passive income – up to 1k/per month
– Find source of passive income – up to 3k/per month
4. Split each milestone to tasks and ideas. Some of them will be duplicated in milestones
– Tasks – what I can do without any additional planing or need only small research e.g. start charge back program account in my bank, find and start good investment.
– Ideas – need more work or additional planing e.g. find clients form my apps. Technically they are set of depended tasks.
5. Set deadlines for tasks and planing session dates for ideas. Still use paper :)
6. Realise task. One by one. Remember to mark task as done. You could realise task from different milestones. Sometimes you could realise sub-tasks from ideas.

When win

1. Write short summary – what was good and help you. Just 5-6 words.
2. When achieve milestone – get small prize. Remember it should be really small. My favorite is bottle of “craft beer”.

When fail

1. Write short summary – what should be done better. Just 5-6 words. DO NOT TRY TO FIND REASON! Because You will write “I Suck” and demotivate yourself.
2. Define new deadline.
3. If you fail second time in the same task. Re define. It is to big.

Congratulation! You read this post. Now go to next task.

Typy nieprymitywne w tabelach Vaadin

November 22nd, 2014

Powrót do konwerterów, ale tym razem w kontekście tabeli.

Problem

Mamy sobie tabelkę wyświetlającą jakieś tam dane. W jednej z kolumn chcemy wyświetlić informację o polu obiektu, które to pole nie jest prymitywne/boxowane/stringiem (jeżeli jest to vaadinowy komponent GUI to zostanie osadzony w komórce, ale to jest wyjątek). Przykładowo:

Listing 1. definicja tabeli

Table table = new Table("Informacje o oddziałach");
table.addContainerProperty("id", Long.class, null);
table.addContainerProperty("name", String.class, "");
table.addContainerProperty("address", Address.class, null);
table.setColumnHeaders("id", "Nazwa", "Adres");

Jak widać w ostatniej kolumnie chcemy umieścić adres oddziału. Niby nic, ale jeżeli przekażemy obiekt bezpośrednio to zostanie na nim wywołana metoda toString i dostaniemy jakieś syfne dane. Oczywiście można pokusić się o napisanie tej metody tak by zwracała dane sformatowane w jakiś domyślny sposób. Można też użyć konwertera.

Konwerter i tabela

Do każdej kolumny w tabeli można podpiąć konwerter w następujący sposób:

Listing 2. dodawanie konwertera

table.setConverter("address", new AddressConverter());

Sam konwerter jest już prosty do napisania. Wystarczy, że zaimplementujemy interfejs Converter<CEL, ŹRÓDŁO>:

Listing 3. implementacja konwertera

private class AddressConverter implements Converter<String, Address> {
	@Override
	public Address convertToModel(String value, 
		Class<? extends Address> targetType,
		Locale locale) throws ConversionException {
		return addressDao.fromString(value);
	}

	@Override
	public String convertToPresentation(Address value, 
		Class<? extends String> targetType, 
		Locale locale) throws ConversionException {
		return value == null ? "" : value.street()+"/"+value.city();
	}

	@Override
	public Class<Address> getModelType() {
		return Address.class;
	}

	@Override
	public Class<String> getPresentationType() {
		return String.class;
	}
}

I w sumie było by to na tyle… interfejs jest paskudny i trochę dużo zbędnego kodu tu, ale inaczej się nie da…

Ale jak masz Javę 8

Można się pokusić o napisanie tego wszystkiego lambdami, co trochę ułatwi życie i może wprowadzić Sajgon w kodzie jak radośnie nazwiecie zmienne, co też zademonstruję.

Na początek potrzebujemy funkcję przyjmująca trzy argumenty:

Listing 4. funkcja o trzech argumentach

@FunctionalInterface
public interface TriFunction<T, U, V, R> {

	R apply(T t, U u, V v);
}

Następnie potrzebujemy fabrykę, która będzie nam produkować nasze konwertery na podstawie lambd:

Listing 5. fabryka konwerterów

public class ConverterFactory {

	public static <T, S> Converter<T, S> get(
		TriFunction<T, Class<? extends S>, Locale, S> toModel,
	        TriFunction<S, Class<? extends T>, Locale, T> toPresentation,
	        Class<S> modelType, 
		Class<T> presentationType) {
		
		return new Converter<T, S>() {
			@Override
			public S convertToModel(T value, 
			                        Class<? extends S> targetType, 
			                        Locale locale) throws ConversionException {
				return toModel.apply(value, targetType, locale);
			}

			@Override
			public T convertToPresentation(S value, 
			                               Class<? extends T> targetType, 
			                               Locale locale) throws ConversionException {
				return toPresentation.apply(value, targetType, locale);
			}

			@Override
			public Class<S> getModelType() {
				return modelType;
			}

			@Override
			public Class<T> getPresentationType() {
				return presentationType;
			}
		};
	}
}

niezłe prawda? To tutaj to są funkcje wyższego rzędu w praktyce. Bardzo przydatne zastosowanie teorii (w językach bardziej funkcyjnych można to zrobić ładniej).

I teraz nasz konwerter można zapisać tak:

Listing 6. konwerter z lambd

table.setConverter("address", 
	new ConverterFactory().get((a, b, c) -> addressDao.fromString(a),
			(a, b, c) -> a == null ? "" : a.street()+"/"+a.city(),
			Role.class,
			String.class));

Elegancko. No prawie ;) Popatrzcie na nazwy zmiennych w lambdach i zapamiętajcie, że nie należy takich rzeczy robić w kodzie produkcyjnym. Niestety Java nie udostępnia mechanizmu parametru domyślnego jak Scala reprezentowanego znakiem _, czy też parametru ignorowanego jak Erlang (ten sam znaczek). Ja wypracowałem sobie konwencję, że jeżeli jakiś parametr jest nieużywany to nazywam go $ jeżeli mam kilka takich parametrów, to liczba znaków $ rośnie. Jak będzie ich więcej niż dwa to oznacza, że trzeba refaktorować.

Dziękuję za to, ze dotrwałeś do końca. Jeżeli spodobał ci się ten wpis to podziel się nim ze znajomymi korzystając z przycisków poniżej. Do tego one służą.

VLOG 1. Leniwa ewaluacja

November 20th, 2014

Robię mały eksperyment.

Jak się podoba? Jest zapewne dużo rzeczy do poprawienia i ulepszenia, ale na początek chyba nie jest źle?

Szybkie tworzenie list z pomocą edytorów w Vaadin

November 18th, 2014

Kolejny wpis z serii jak to zrobić w Vaadin by nie bolało.

Załóżmy, że chcemy sobie spiąć pole naszego bean-a z ComboBox-em. Oczywiście coś w rodzaju:

Listing 1. To nie zabangla

ComboBox listofSth = fieldGroup.buildAndBind("Lista wyboru", "oneOfMany", ComboBox.class);

Walnie nam wyjątkiem com.vaadin.data.fieldgroup.FieldGroup$BindException i nie ma w tym nic dziwnego ponieważ skąd framework ma wiedzieć czy spinana wartość ma sens w kontekście potencjalnej zawartości listy czy też nie.

Rozwiązanie jest proste. Wystarczy użyć metody bind:

Listing 2. A to już tak

ComboBox listOfSth = new ComboBox("Lista wyboru");
fieldGroup.bind(listOfSth, "oneOfMany");

Takie coś ku pamięci…

Guice, Vaadin 7 i Shiro – ale to już było

November 17th, 2014

Trochę retrospektywny wpis, bo o podobnym miksie wspominałem już kiedyś. Wtedy chodziło jednak o Vaadin 6. Późnej popełniłem wpis o integracji Vaadin 7 i Guice.

Dziś uzupełnię go o krótkie know-how w temacie integracji z Shiro. Naprawdę krótkie, bo bazując na poprzednich wpisach na pewno dasz sobie radę z takimi banałami tworzenie konfiguracji mavena czy dodanie Guice do aplikacji Vaadin 7.

Najważniejszym elementem całej zabawy jest w naszym przypadku odpowiednie napisanie web.xml. Taki integracyjny plik będzie wyglądał tak:

Listing 1. web.xml z filtrami Guice i Shiro



    komsell
    
        
            Vaadin production mode
        productionMode
        false
    
    
        org.apache.shiro.web.env.EnvironmentLoaderListener
    
    
        ShiroFilter
        org.apache.shiro.web.servlet.IniShiroFilter
    
    
        GuiceFilter
        com.tyrsoft.komsell.guice.MyGuiceFilter
    
    
        ShiroFilter
        /*
        REQUEST
        FORWARD
        INCLUDE
        ERROR
    
    
        GuiceFilter
        /*
    

Najważniejszym elementem jest tu odpowiednie skonfigurowanie Shiro. Będzie ono pracowało poza trybem ASYNC co oznacza, że w aplikacji nie udostępniamy mechanizmu wywołań asynchronicznych. Wynika to z faktu, że Guice nie potrafi ich prawidłowo obsłużyć (chyba, że znacie jakiś przykład działającej konfiguracji).

Takie kuźwa przemyślenie

October 30th, 2014

Po wprowadzeniu chmielowego…

Jeżeli ktoś mówi, że ozusowanie umów zleceń to dokopanie najbiedniejszym to ja się kurwa zastanawiam czemu zlecenie == bieda? Przecież, skoro wszystkim tym jebanym januszom biznesu zależy na tym by społeczeństwo było zamożne, wystarczyło by ludziom dać te pieprzone UoP jako pierwsza opcję do wyboru.
Niestety cebulaki to taka grupa, która zakłada, że jak po roku z warzywniaka nie ma na Audi q7 to chuja a nie interes.
Dlatego biedzie najbiedniejszych (tych co nie wpychają się w tą biedę na własne życzenie chlając, ćpając i pierdoląc świat) najbardziej winny jest bieda biznes cebulowy, który myśli kategoriami ja-moje-najmojsze….

Pewien prosty lifehack I

October 7th, 2014

Dziś nie technicznie, ale hakiersko. W necie możecie znaleźć wiele stron z różnymi lifehack-ami, czyli rozwiązaniami prostych życiowych problemów. Czasami są to porady bardziej ogólne, czasami rozwiązania dla konkretnych wrzodów. Osobną grupą są serwisy DIY (ang. Do It Yourself), poświęcone konstruowaniu różnych poręcznych urządzeń, czy też “rozwiązań” do domu-ogrodu-biura.

Te kurewskie owocówki

Muszki owocówki, to jakaś plaga. Szczególnie teraz gdy owoców jest dużo, przerabiamy je i suszymy. Można z tym walczyć na kilka sposobów. Mój jest bardzo prosty. Bierzemy małą butelkę po wodzie mineralnej. Wrzucamy na dno ze dwa ogryzki i dolewamy trochę soku. Stawiamy w jakimś kącie. Muszki wlezą, ale już nie wylezą. Raz na pewien czas wystarczy butelkę zamknąć (niezbyt szczelnie) i wyrzucić. Jeżeli w okolicy są jakieś muszki poza naszą pułapką (nie wlazły, ale zwabiły się) to polewamy raidem. W jednym koncie jest bezpieczniej niż psikać po całym mieszkaniu.

A i jak byście czuli potrzebę łapania owocówek, to metoda “szybki i wściekły”, czyli klepanie łapami jak popadnie nie działa. Jak chcecie złapać owocówkę to róbcie to powoli. Na szybki ruch reaguje ona instynktownie i bardzo szybko. Na powolny nie reaguje prawie wcale (nie odbiera go jako zagrożenia, a jak się połapie co i jak to już jest za późno).

Guava Event Bus – bo Obserwator z JDK jest naprawdę chujowy

October 5th, 2014

Guava poza całkiem przyjemnym zestawem idiomów funkcyjnych czy zastępników kodu oraz narzędzi wszelakich posiada też pewne przydatne pakiety ;)

Jednym z nich jest pakiet com.google.common.eventbus, który zawiera kompletną szynę zdarzeń. Jest to idealny zastępnik dla implementacji wzorca obserwatora z JDK.

I na początek jeszcze dwa słowa o tym dlaczego porównuję event busa do obserwatora. Ponieważ event bus jest specyficzną formą implementacji tego wzorca. Przy czym obserwowany obiekt nic nie wie o rzeczywistych obserwatorach. Jedyne co robi to wysyła informację do punktu dostępowego, że zaistniała jakaś godna uwago zmiana. Obserwatorzy muszą tylko wiedzieć z jakiego typu komunikatem będą mieli do czynienia.

Dlaczego Obserwator jest chujowy

Nie chodzi o sam wzorzec, ale o to jak został zaimplementowany w Javie. A został zaimplementowany bardzo źle. Problem leży w klasie Observable, która jest klasą. Zatem jeżeli chcesz użyć wzorca obserwatora to musisz ją rozszerzyć, a to zamyka w praktyce hierarchię dziedziczenia. W sumie inaczej tego nie dało się zrobić, bo model dziedziczenia w Javie jaki jest taki jest i lepszy nie będzie.
Co prawda można było to rozwiązać w trochę inny sposób np. za pomocą interfejsu, domyślnej implementacji i zalecenia implementacji przez delegację, lecz nikt na to nie wpadł. Szkoda, bo jest to kolejny mały kamyczek, który uwiera gdy przywdziejesz buty programisty Java.

Pierwsze kroki

Najprostszy program demonstrujący działanie Guava Event Bus będzie wyglądał mniej więcej tak:

Listing 1. Prosty program

public class App {

	public static void main(String[] args) {
		EventBus eb = new EventBus();
		eb.register(new StringEventHandler());
		eb.register(new IntegerEventHandler());
		eb.post(new StringEvent("Hello World!"));
		eb.post(new IntegerEvent(42));

	}
}

Co tu się dzieje? Sercem systemu jest klasa EventBus. Ona reprezentuje szynę. Można jej nadać identyfikator. Można utworzyć wiele instancji tej klasy i mieć wiele szyn. Można w końcu użyć AsyncEventBus, gdzie w konstruktorze przekazujemy Executor i otrzymujemy system współbieżny.
Kolejne dwie linie to rejestracja obiektów nasłuchujących. Jak to wygląda w bebechu to powiem za chwilę. Generalnie obiektem nasłuchującym może być każdy obiekt. Ostatnie dwie linie to wysyłanie zdarzeń. Tu podobnie jak w przypadku obiektów nasłuchujących można wysłać dowolny obiekt.

Widać tu pewną wadę tego rozwiązania. Otóż brak silnego powiązania pomiędzy kodem naszych obiektów nasłuchujących, a Guavą powoduje, że trzeba ręcznie rejestrować te obiekty w ramach szyny. Z drugiej strony zawsze pozostaje AOP na konstruktorze ;)

Obiekty nasłuchujące aka obserwatorzy

Obiekt nasłuchujący będzie powiadamiany o zdarzeniu przez szynę (tę w której go zarejestrowaliśmy). Co powoduje, że szyna rozróżnia kto ma dostać jakie powiadomienie i jak ma być ono obsłużone (którą metodę należy wywołać). Przyjrzyjmy się jednemu z naszych obserwatorów.

Listing 2. Obiekt nasłuchujący

class StringEventHandler {

	@Subscribe
	public void handleEvent(StringEvent stringEvent) {
		System.out.println(stringEvent.get());
	}

}

Cała “magia” rozwiązania leży sobie w adnotacji @Subscribe. W momencie rejestracji obiektu w szynie ta wyszukuje wszystkie metody oznaczone ta adnotacją. Metody muszą być publiczne i mieć tylko jeden argument. Jeżeli nie będzie spełniony pierwszy warunek, to metoda zostanie pominięta (pod spodem jest wywołanie getMethods, które zwróci metody publiczne). Jeżeli metoda będzie miała więcej argumentów albo nie będzie miała ich wcale to dostaniemy IllegalArgumentException. Na podstawie typu argumentu szyna będzie decydowała czy dane zdarzenie jest przeznaczone dla tego obserwatora.

Obiekty zdarzeń

Jak napisałem wcześniej zdarzenia mogą być dowolnego typu, a zatem nie będę dokładnie omawiał ich konstrukcji. Warto jednak omówić jakie cechy powinna mieć dobra klasa reprezentująca zdarzenie.
Po pierwsze, i oczywiste, powinna być niezmienna. Klasy niezmienne mają tą przyjemną własność, że możemy przyjąć iż nikt nam nic w niej nie namieszał.
Po drugie na zdarzenia nie nadają się obiekty reprezentujące typy prymitywne czy String. Choć obiekty te są niezmienne to jednak bezpośrednia wymiana zdarzeń za ich pomocą będzie trudna. Musimy wtedy dopisywać jakieś parsery, matchery diabli wiedzą co, żeby mieć pewność to co otrzymaliśmy rzeczywiście potrafimy obsłużyć.
Po trzecie w większych systemach warto “zainwestować” i wyposażyć nasze obiekty w UUID. Szczególnie jeżeli stosujemy “przepychanie” zdarzeń po między różnymi szynami.

Hierarchia i typy generyczne, a zdarzenia

Poza wspomnianymi już wcześniej dobrymi praktykami związanymi z tworzeniem zdarzeń należy jeszcze przyjrzeć się jak dziedziczenie wpływa na obsługę zdarzeń. Guava dość dobrze radzi sobie z klasycznym modelem dziedziczenia opartym o rozszerzanie klas. Dobrze widać to w poniższym programie (typy wiadomości dobrane tylko po to by nie komplikować)

Listing 3. Hierarchia klas zdarzeń

public class HierarchyApp {

	public static void main(String[] args) {
		EventBus eb = new EventBus();
		eb.register(new IntHandler());
		eb.register(new NumberHandler());
		eb.post(1);
		eb.post(2L);
	}
}

class IntHandler{

	@Subscribe
	public void forInt(Integer i){
		System.out.println("Integer " + i);
	}

}
class NumberHandler{

	@Subscribe
	public void forInt(Number i){
		System.out.println("Number " + i);
	}

}

W przypadku pierwszej wiadomości, która jest typu Integer, który rozszerza Number w efekcie oba handlery otrzymają tą wiadomość. Druga wiadomość jest typu Long zatem otrzyma go tylko handler obsługujący nad typ.

Drugim modelem dziedziczenia jest generyczność. Z tym Guava całkowicie sobie nie radzi. Jeżeli w naszym pierwszym przykładzie zamienilibyśmy obsługę StringEvent i IntegerEvent tak by zamiast tych klas przyjmowały generyczny Event<T&gy; to oba obiekty nasłuchujące otrzymały by oba komunikaty. Efekt… kboom!

DeadEvent

Ostatnim podstawowym elementem do omówienia jaki nam został jest sposób obsługi wiadomości nieobsłużonych. Najprostszym przypadkiem takiej wiadomości jest przypadek gdy wysłaliśmy wiadomość, ale nie ma obserwatora, który może ja obsłużyć. W takim przypadku wiadomość zostaje opakowana w klasę DeadEvent i wysłana do klasy, która potrafi ją obsłużyć (sami musimy ją napisać i zarejestrować w szynie).

Podsumowanie

Guava Event Bus to świetne rozwiązanie pozwalające na wyrugowanie z naszego kodu silnej zależności od obserwatora z podstawowego API. Zaletą jest też stosunkowa prostota wdrożenia tego rozwiązania oraz całkiem rozsądna wydajność.
Wadą brak obsługi typów generycznych.

Jak spodobał Ci się ten wpis to użyj guzików poniżej by podzielić się nim ze swoimi znajomymi.


Translate »