Załóżmy, że staramy się pisać zgodnie ze sztuką funkcyjnego programowania (na ile pozwala na to Java). Mamy więc same klasy immutable, korzystamy z kolekcji vavr.io. Każda zmiana stanu danej klasy poprzez jakąś metodę powoduje utworzenie nowego obiektu tej klasy. No i teraz mamy taką sytuację.
Mamy klasę State, która częścią czegoś w stylu maszyny stanów.
final class State {
//konstruktor
private final State nextState;
public State doSomething(String message) {
// tworzymy nowy obiekt state (kopiując nextState, bo on się nie zmienia) + jakaś zmiana tego stanu
return new State(message, nextState);
}
}
stany przechowujemy w HashMapie, która trzyma: ID oraz aktualny stan dla danego ID. Dla ułatwienia niech będzie po prostu tak:
Map<String, State> map = HashMap[...] <== jest to mapa z vavr.io
Dodatkowe założenia:
- Mapa ze stanami (oraz "nextState") generowana jest wcześniej.
- Istnieje metoda (niżej), która dostaje request i message. Jeżeli znajdzie w mapie State (value w mapie) powiązany z request (key w mapie), wykona na tym State jakąś operację zmieniając jego stan, a następnie zapisuje sobie następnika tego stanu w hashmapie.
public void exampleMethod(String request, String message) {
(pomińmy, że tutaj się może coś sypnąć - to nie jest ważne)
return
Try.of(() -> map.get(request).get())
.andThenTry(state -> state.doSomething(message))
.map(State::getNextState)
.map(nextState -> map.put(request, nextState))
.get();
}
I załóżmy, że po zakończeniu działania programu mamy w HashMapie takie obiekty typu State, które nie posiadają nextState.
Dochodzi nowa potrzeba: chcemy, po zakończeniu programu, mieć możliwość cofnięcia się z ostatniego stanu (czyli tego w którym aktualnie przebywa w HashMapie pod danym kluczem) do pierwszego. Pierwsza myśl: dodać (analogicznie do nextState) pole prevState i w momencie tworzenia mapy łączyć w listę dwukierunkową. Od teraz nasz State wygląda tak:
final class State {
//konstruktor
private final State nextState;
private final State prevState;
public State doSomething(String message) {
// tworzymy nowy obiekt state (kopiując referencję aktualnego nextState, bo on się nie zmienia) + jakaś zmiana tego stanu
return new State(message, ????, prevState);
}
}
**I teraz problem:
**
Metoda doSomething() generuje nowy obiekt typu State (na podstawie obiektu State na którym ta metoda zostala wykonana).
Teraz dochodzimy do sytuacji, że jeżeli mamy listę dwukierunkową z referencjami:
Stan A <=> Stan B <=> C <=> ... => empty.state
I wykonujemy operację na Stanie B tym samym tworząc nowy obiekt Stan B2 musimy:
- Stworzyć nowy Stan A2 (którego następnik wskazuje na nowy Stan B2)
- Stworzyć nowy Stan C2 (którego poprzednik wskazuje na nowy Stan B2)
- Stan B2 wskazuje na nowe stany A2(poprzednik) oraz C2 (następnik)
I analogicznie musimy przerobić całą listę dwukierunkową (C2 spowoduję zmianę w stanie D, itd..)
Czyli dla potrzeby zmiany jednego pola w klasie State potrzebujemy zrobić od nowa strasznie skomplikowany obiekt.
I teraz możliwe rozwiązania:
-
Zaciskamy zęby i tworzymy jakąś skomplikowaną metodę, która nam to potworzy.
-
Delikatnie popuszczamy i wystawiamy setera (doSomething założmy, że finalnie ustawia po prostu jedno pole w klasie więc możemy to zmienić na setSomething(request)).
-
Wyciągamy listę dwukierunkową z klasy State - jak w takim przypadku najlepiej trzymać index w którym aktualnie stanie się znajdujemy? Możemy wtedy trzymać mapę: Map<String, LinkedList<State>>, ale co właśnie z jakimś indeksem, który nam powie "w tym stanie aktualnie jestem".
-
Może trzymać drugą strukturę do zapamiętywania i pozbyć się wtedy pola prevState?
@jarekr000000 może coś podpowiesz? :P
Uff.
teez