Wyobraźmy sobie kod, metodę, która przyjmuje dwa parametry. Pierwszy parametr jest następnie wykorzystywany w instrukcji warunkowej, która znowuż wykonuje jakiś kod związany z drugim parametrem. Drugi parametr jest wykorzystywany tylko po spełnieniu warunku. Znajomo brzmi?
Drugi parametr jest wyliczany w wyniku jakiejś długo trwającej operacji. Nasze zadanie brzmi zrobić coś co spowoduje, że całość zacznie działać szybciej. Przy czym:

  • Nie możemy zmienić zależności obiektów. W tym przekazywać ich jako parametrów.
  • Długo trwająca operacja musi zostać wykonana wielokrotnie tzn. nie możemy sobie jej wykonać raz na starcie i cachować wyniku.

Poniżej strasznie prymitywny kod, który mniej więcej emuluje nam to co chcemy:

Listing 1. Przykładowy kod

public class App {

	private LongOpService los;

	public App() {
		los = new LongOpService();
	}

	public static void main(String[] args) {
		App a = new App();
		Random r = new Random();
		for (int i = 0; i < 10; i++) {
			a.someOp(r.nextInt(10),
					a.los.longOp());
		}
	}

	public void someOp(int param, int longOpResult) {
		if (param < 2)
			System.out.println(longOpResult);
		else
			System.out.println(param);
	}
}

class LongOpService {

	public int longOp() {
		System.out.println(\"Long Op!\");
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return 42;
	}
}

Interesuje nas pętla w metodzie main, która emuluje wielokrotne wywołanie metody someOp, która znowuż wymaga w pewnych przypadkach wyniku longOp. Na kolejnych listingach będziemy obracać się w okolicach tej pętli.

Brute force - NullObject

Załóżmy, bardzo optymistycznie, że metoda longOp ma ograniczony zbiór wartości, które może zwrócić. Względnie zamiast inta zwraca coś mniej prymitywnego… i można wstawić null. Możemy wtedy zastosować wzorzec Null Object.

Listing 2. Null Object

for (int i = 0; i < 10; i++) {
	int param = r.nextInt(10);
	int longOpResult = -1;
	if (param < 2)
		longOpResult = a.los.longOp();
	a.someOp(param, longOpResult);
}

Tu rolę obiektu użytego zamiast null spełnia wartość -1. Strasznie to brzydkie. Jest lepsza metoda. Może by tak udało się przenieść wywołanie długiej operacji do ciała metody someOp. No tylko, co z tymi zależnościami…

Opóźnienie wywołania

Może jednak udało by się to zrobić jeżeli sięgniemy po pewne myki znane ze Scali. Jest tam sobie taka konstrukcja:</p>

Listing 3. Co chcemy wykorzystać ze Scali


def someOp(param: Int, longOpResult: => Int) = {
  
}

Zapis ten oznacza “wylicz drugi parametr jak go będziesz potrzebować”. Na czym polega myk? W językach imperatywnych by wywołać jakąś metodę komputer wyznacza wszystkie parametry. Co oznacza, że za każdym razem odpala naszą operację i czeka… czeka… czeka… Jako, że nie możemy przekazać bezpośrednio obiektu LongOpService (bo przyjdzie sprawdzanie zależności pomiędzy modułami i nas zje) to zostaje nam tylko jedna sensowna opcja.

Listing 4. Nasze rozwiązanie

for (int i = 0; i < 10; i++) {
	a.someOp(r.nextInt(10), new Callable<Integer>() {
		@Override
		public Integer call() {
			return a.los.longOp();
		}
	});
}

W tym przypadku wywołanie długo trwającej operacji nastąpi dopiero po spełnieniu warunku. Przy czym Callable nie jest najgenialniejszym rozwiązaniem ponieważ rzuca wyjątkiem.

Dobra. Tyle na dziś. Choć rozwiązanie nie jest niczym odkrywczym dla pr0, to jednak może się komuś przydać.