Czy wzorzec obserwatora jest odpowiedni, gdy obserwatorzy nie są od siebie niezależni?

9

Mam class Carco ma 2 właściwości: int pricei boolean inStock. Posiada również Listod abstract class State(pustej klasie). Istnieją dwa stany, które można zastosować w samochodzie i każdy jest reprezentowany przez własną klasę: class Upgrade extends Statei class Shipping extends State.

A Carmoże pomieścić dowolną liczbę każdego z 2 stanów. Stany mają następujące zasady:

  • Upgrade: dodaje 1się do ceny za każdy stan zastosowany do samochodu po sobie.
  • Shipping: jeśli Shippingna liście jest co najmniej 1 stan, to inStockjest ustawione na false.

Na przykład zaczynając od price = 1i inStock = true:

add Shipping s1    --> price: 1, inStock: false
add Upgrade g1     --> price: 1, inStock: false
add Shipping s2    --> price: 2, inStock: false
add Shipping s3    --> price: 3, inStock: false
remove Shipping s2 --> price: 2, inStock: false
remove Upgrade g1  --> price: 1, inStock: false
remove Shipping s1 --> price: 1, inStock: false
remove Shipping s3 --> price: 1, inStock: true

Myślałem o wzorcu obserwatora, w którym każda operacja dodawania i usuwania powiadamia obserwatorów. Miałem na myśli coś takiego, ale to nie jest zgodne z zasadami, które ustanowiłem:

abstract class State implements Observer {

    public abstract void update();
}

class Car extends Observable {

    List<State> states = new ArrayList<>();
    int price = 100;
    boolean inStock = true;

    void addState(State state) {

        if (states.add(state)) {
            addObserver(state);
            setChanged();
            notifyObservers();
        }
    }

    void removeState(State state) {

        if (states.remove(state)) {
            deleteObserver(state);
            setChanged();
            notifyObservers();
        }
    }
}

class Upgrade extends State {

    @Override
    public void update(Observable o, Object arg) {

        Car c = (Car) o;
        int bonus = c.states.size() - c.states.indexOf(this) - 1;
        c.price += bonus;
        System.out.println(c.inStock + " " + c.price);
    }
}

class Shipping extends State {

    @Override
    public void update(Observable o, Object arg) {

        Car c = (Car) o;
        c.inStock = false;
        System.out.println(c.inStock + " " + c.price);
    }
}

Oczywiście to nie działa. Kiedy a Shippingjest usuwane, coś musi sprawdzić, czy istnieje inne ustawienie stanu inStockna false, więc usunięcie Shippingnie może po prostu inStock = true. Upgradezwiększa się priceprzy każdym połączeniu. Następnie dodałem stałe wartości domyślnych i spróbowałem ponownie je obliczyć.

W żadnym wypadku nie próbuję narzucać żadnego wzorca, staram się tylko znaleźć rozwiązanie dla powyższych wymagań. Należy zauważyć, że w praktyce Carzawiera wiele właściwości i istnieje wiele stanów, które można zastosować w ten sposób. Pomyślałem o kilku sposobach:

  1. Ponieważ każdy obserwator odbiera Car, może patrzeć na wszystkich innych aktualnie zarejestrowanych obserwatorów i na tej podstawie dokonać zmiany. Nie wiem, czy mądrze jest uwikłać takich obserwatorów.
  2. Po dodaniu lub usunięciu obserwatora Carnastąpi ponowne obliczenie. Jednak to ponowne obliczenie będzie musiało zostać wykonane na wszystkich obserwatorach, niezależnie od tego, który został właśnie dodany / usunięty.
  3. Mieć zewnętrzną klasę „manager”, która wywoła metody dodawania i usuwania i wykonuje ponowne obliczenia.

Jaki jest dobry wzorzec projektowy do wdrożenia opisanego zachowania i jak by to działało?

użytkownik1803551
źródło
1
Celem wyodrębnienia stanu do jego własnej klasy jest oddzielenie logiki, która nie wchodzi w interakcje, upraszczając kod. Jednak twoja logika biznesowa dyktuje, że ich logika jest połączona, a zatem musisz z powrotem połączyć się z linkiem, co powoduje okropny bałagan. Problemem nie jest tutaj wzorzec obserwatora, lecz wzorzec ról, który stosujesz w stanie.
ArTs
Jak rozwiązałbyś problem, gdybyś robił to ręcznie?
James Youngman
@JamesYoungman Skończyłem z moją trzecią opcją - menedżerem zewnętrznym. Zasady pisania na papierze w tym przypadku są proste, ale opcje, które oferuje język, aby je wdrożyć, są w tym przypadku ograniczone . Stąd potrzeba wzorca projektowego. Myślenie o tym, „jak zrobiłbyś to ręcznie”, działa bardziej w przypadku algorytmów niż w przypadku stosowania jasnego zestawu reguł.
user1803551,
@ user1803551 Wybrałeś dobrze.
Tulains Córdova,
Posiadaj jeden moduł obsługi zdarzeń dla wszystkich zdarzeń. Ten moduł obsługi jest po prostu punktem wejściowym do ponownego obliczenia pełnego stanu obiektu. Jest to typowy problem. Widać to podczas pracy z formą „od góry do dołu, od lewej do prawej” - wszystko jest w porządku, ale zmiana czegoś w środku nie przelicza się poprawnie. Jeśli kiedyś zapytasz „jak mogę zagwarantować obsługę zdarzenia?”, Teraz wiesz, co musisz zrobić.
radarbob

Odpowiedzi:

1

Obserwatorzy działaliby świetnie, gdybyś inaczej rozłożył system. Zamiast uczynić państwa samymi obserwatorami, można utworzyć 2 nowe klasy, które będą „obserwatorami zmiany stanu”: jeden obserwator zaktualizuje „cenę”, a drugi zaktualizuje „inStock”. W ten sposób będą one niezależne, jeśli nie będziesz mieć reguł dotyczących ceny w zależności od inStock lub odwrotnie, tj. Jeśli wszystko można obliczyć, patrząc tylko na zmiany stanu. Ta technika nazywa się „pozyskiwaniem zdarzeń” (na przykład patrz: https://ookami86.github.io/event-sourcing-in-practice/ ). Jest to wzorzec w programowaniu, który ma kilka znaczących aplikacji.

Odpowiadając na bardziej ogólne pytanie, czasami naprawdę masz zależności między obserwatorami. Na przykład możesz chcieć, aby jeden obserwator zareagował przed drugim. W takim przypadku zazwyczaj możliwe jest wykonanie niestandardowej implementacji klasy Observable w celu uporządkowania zamówień lub zależności.

battlmonstr
źródło
0

Skończyło się na opcji 3 - przy użyciu zewnętrznego menedżera. Menedżer jest odpowiedzialny za dodawanie i usuwanie StateS z CarS i powiadamiania obserwatorów, kiedy te zmiany zachodzą.

Oto jak zmodyfikowałem kod. Usunąłem Observable/ Observerz JDK, ponieważ wykonuję własną implementację.

Każdy Statezachowuje odniesienie do Carzastosowanego.

abstract class State {

    Car car;

    State(Card car) { this.car = car; }

    public abstract void update();
}

class Upgrade extends State {

    @Override
    public void update() {

        int bonus = car.states.size() - car.states.indexOf(this) - 1;
        car.price += bonus;
        System.out.println(car.inStock + " " + car.price);
    }
}

class Shipping extends State {

    @Override
    public void update() {

        car.inStock = false;
        System.out.println(car.inStock + " " + car.price);
    }
}

Carzachowuje tylko swój stan (aby uniknąć pomyłek: właściwości) i nie obsługuje dodawania i usuwania States:

class Car extends Observable {

    List<State> states = new ArrayList<>();
    int price = 100;
    boolean inStock = true;
}

Oto kierownik. Wyprzedził Car(obserwowalny) obowiązek zarządzania swoimi State(obserwatorami).

class StatesManager {

    public void addState(Card car, State state) {

        car.states.add(state);
        for (State state : car. states)
            state.update;
    }

    public void removeState(Card car, State state) {

        car.states.remove(state);
        for (State state : car. states)
            state.update;
    }
}

Kilka rzeczy, o których należy pamiętać:

  • Wszyscy obserwatorzy są powiadamiani o każdej zmianie. Bardziej sprytny schemat dystrybucji zdarzeń może wyeliminować niepotrzebne wywołania do updatemetody obserwatora .
  • Obserwatorzy mogą chcieć ujawnić więcej metod podobnych do „aktualizacji” na różne okazje. Jako przykład mogą podzielić obecną updatemetodę updateOnAddi updateOnRemovejeśli są zainteresowani tylko jedną z tych zmian. Następnie metody addStatei removeStatezostaną odpowiednio zaktualizowane. Wraz z poprzednim punktem podejście to może stać się solidnym, rozszerzalnym i elastycznym mechanizmem.
  • Nie określiłem, co daje instrukcję dodawania i usuwania States, a kiedy tak się dzieje, ponieważ nie ma to znaczenia dla pytania. Jednak w przypadku tej odpowiedzi należy rozważyć następujące kwestie. Ponieważ Stateteraz musi zostać utworzony z jego Car(bez ujawnienia pustego konstruktora) przed wywołaniem metody menedżera, metody addStatei removeStatenie muszą brać Cari mogą po prostu odczytać state.car.
  • Obserwatorzy są domyślnie powiadamiani w kolejności rejestracji o obserwowalnym. Można określić inną kolejność.
użytkownik1803551
źródło