Czas na kilka wpisów poświęconych różnym aspektom biblioteki Googel Guava. Jak zresztą zapowiedziałem w poprzednim wpisie karnawał minie nam z tą biblioteką.

Dziś przyjrzymy się temu co najczęściej jest wykorzystywane z biblioteki, czyli idiomom funkcyjnym. Dokładniej najmniej znanemu z nich, czyli interfejsowi Supplier i klasie narzędziowej Suppliers. Jest to najmniej wykorzystywana para z funkcyjnej części tej biblioteki. Dlatego sądzę, że warto zacząć zabawę właśnie od niej.

Interfejs Supplier

Nie ma w nim nic szczególnego. Zawiera jedną metodę get, która ma dostarczać obiekty klasy T (jest generyczna). Dokumentacja wprost mówi, że może on pełnić rolę dostawcy, fabryki czy generatora. Generalnie klasa implementująca ten interfejs powinna być traktowana jako „coś co dostarcza obiekty”. Jednocześnie sam interfejs nie daje żadnych gwarancji co do działania, ale nie narzuca też ograniczeń.
Tu dwa słowa na temat pewnej niezręczności, która wkrada się w tym miejscu. Interfejs ten jest funkcjonalnie tożsamy z interfejsami Provider z Guice czy z JSR-330 (DI). Powoduje to, że chcąc dostosować naszą implementację do wymagać DI musimy tworzyć pseudo-dekoratory. Trochę to złe. Trochę bardzo.

Klasa Suppliers

Sam interfejs nie daje nam nic ponad to, że pozwala na w miarę spójne tworzenie kodu zgodnie z zasadami dobrego projektowania opartego o interfejsy. Znacznie ciekawszym elementem jest klasa Suppliers, która ma kilka metod statycznych dostarczających wsparcie dla interfejsu.

Na początek stwórzmy prostą implementację interfejsu, która będzie dostarczała nam dane do naszej zabawy.

Listing 1. Implementacja interfejsu

class SimpleCurrentDateAsStringSupplier implements Supplier<String>{
	@Override
	public String get() {
		Date d = new Date();
		return d.toString();
	}
}

Nie ma tu nic nadzwyczajnego. Jeżeli wrzucimy taki kod do na przykład TimerTask i odpalimy co sekundę wywołanie metody get otrzymamy prymitywny zegar. Przyjrzyjmy się jednak co możemy osiągnąć wykorzystując klasę Suppliers

Przygotujmy sobie jeszcze aplikację testową, która będzie symulować działanie prawdziwej z wykorzystaniem klas Timeri TimerTask

Listing 2. Symulator

public class SupplierExample {

	public static void main(String[] args) {

		Timer timer = new Timer();
		timer.schedule(new SupplierTT(supplier)
				, 1000L, 1000L);
	}
}

class SupplierTT extends TimerTask {

	private final Supplier<?> supplier;

	SupplierTT(Supplier<?> supplier) {
		this.supplier = supplier;
	}

	@Override
	public void run() {
		System.out.println(supplier.get());
	}
}

Do zmiennej supplier będziemy wrzucać wyniki wywołania kolejnych metod klasy Suuppliers

Metoda memoize

Metoda ta tworzy instancję dostawcy, która jeden raz wywołuje metodę get zapamiętuje jest wynik i następnie za każdym razem zwraca ten sam obiekt. Znajome? Taka sprytna metoda na uzyskanie singletonu. Argumentem tej metody jest oryginalny Supplier, który musimy samodzielnie naklepać.

Metoda memoizeWithExpiration

Jest to metoda podobna do poprzedniej lecz wyposażona w dodatkowe parametry pozwalające na określenie po jakim czasie następuje kolejne wywołanie metody get z oryginalnej implementacji. Jest to wygodne rozwiązanie jeżeli wiemy, że dane co pewien czas się zmieniają, ale nie zależy nam na ich aktualności przez cały czas.

Metoda ofInstance

Metoda ta zwraca obiekt dostawcy, który zawsze będzie zwracał ten sam, przekazany jako argument, obiekt. Jest to kolejna opcja na uzyskanie singletonu. Jest to też wygodne rozwiązanie gdy tworzymy dane testowe.

Metoda synchronizedSupplier

Tym razem coś troszkę innego. Metoda ta zwraca obiekt dostawcy, który w momencie wywołania get zakłada blokadę na oryginalnym obiekcie czyniąc je bezpiecznym w środowisku wielowątkowym.

Metoda compose

Moim zdaniem najciekawsza i dająca najwięcej możliwości metoda w klasie. Przyjmuje dwa parametry. Drugim jest oryginalny dostawca, a pierwszym obiekt implementujący interfejs Function. Zwraca dostawcę, który ma dość użyteczny przepływ. W momencie wywołania metody get z dekoratora wywoływana jest metoda get dekorowanego dostawcy. Wyniki tego wywołania są przekazywane do funkcji z pierwszego parametru, a wynik jej działania jest zwracany przez dekorator.
Mówiąc prościej metoda ta dostarcza funkcjonalność pozwalającą na transformowanie jednego dostawcy na innego z wykorzystaniem podanej funkcji.
Gdzie można to zastosować? Idealnym miejscem jest zastąpienie konwerterów obiektów DTO za pomocą takiego dostawcy. Zamiast ręcznego konwertowania obiektów pozyskanych z jakiegoś serwisu wystarczy wstrzyknąć zamiast tego serwisu naszego dostawcę uzyskanego za pomocą tej metody, który będzie używać odpowiedniej funkcji konwertującej. Przykład poniżej

Listing 3. Zamiana znacznika czasu na String

/**
* Zwraca datę jako Long. Nasz dostawca/usługa.
*/
class SimpleCurrentDateAsLongSupplier implements Supplier<Long> {
	@Override
	public Long get() {
		Date d = new Date();
		return d.getTime();
	}
}
// Kod w starym stylu:
class DateDTO {
	final String date;

	DateDTO(String date) {
		this.date = date;
	}
}

class DateLongToDateDTOConverter {

	public DateDTO convert(Long dateLong) {
		return new DateDTO((new Date(dateLong)).toString());
	}
}
// kod w nowym stylu
Function<Long, String> dateLongToDateString = new Function<Long, String>() {  
                                                                             
	@Override                                                                   
	public String apply(Long input) {                                           
		return input + ":" + (new Date(input)).toString();                         
	}                                                                           
};                                                                           
Supplier<String> compose = Suppliers                                         
		.compose(dateLongToDateString, new SimpleCurrentDateAsLongSupplier());

Jak widać choć sama metoda zamiany obiektów nie zmieniła się to dzięki wstrzyknięciu naszego dostawcy z funkcją możemy wyeliminować obiekt DTO i zamiast niego użyć dostawcy „bezpośrednio” wraz z małą funkcją „po środku”

Metoda supplierFunction

Ostatnia metoda, która jest w naszej klasie pozwala na „przetłumaczenie” interfejsu Supplier na interfejs Function. Tworzy ona funkcję, która jako argument przyjmuje instancję dostawcy i wywołuje metodę get. Całość oznaczona jest adnotacją @Beta i niech Omnissiah błogosławi tego, który znajdzie zastosowanie dla tej metody.

Podsumowanie

Trochę nam tu zrzut API wyszedł, ale od czegoś trzeba zacząć, a omawiane zagadnienie jest nudne. Będzie się to rozkręcać w miarę jak przejdziemy do ciekawszych elementów Guavy.
Na razie zapraszam do repozytorium gdzie od teraz będzie umieszczany kod ze wszystkich wpisów na blogu.