„Uleniwienie” parametru jako sposób na długie wywołania
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ć.