Ist das Beobachtermuster geeignet, wenn die Beobachter nicht unabhängig voneinander sind?

9

Ich habe eine, class Cardie 2 Eigenschaften hat: int priceund boolean inStock. Es enthält auch eine Listvon abstract class State(leere Klasse). Es gibt 2 Zustände, die auf das Auto angewendet werden können und jeder wird durch seine eigene Klasse dargestellt: class Upgrade extends Stateund class Shipping extends State.

A Carkann eine beliebige Anzahl der beiden Zustände enthalten. Die Staaten haben folgende Regeln:

  • Upgrade: erhöht 1den Preis für jeden Zustand, der auf das Auto nach sich selbst angewendet wird.
  • Shipping: Wenn Shippingdie Liste mindestens 1 Status enthält, inStockwird auf gesetzt false.

Zum Beispiel beginnend mit price = 1und 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

Ich dachte über das Beobachtermuster nach, bei dem jeder Vorgang zum Hinzufügen und Entfernen Beobachter benachrichtigt. Ich hatte so etwas im Sinn, aber es entspricht nicht den Regeln, die ich aufgestellt habe:

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);
    }
}

Offensichtlich funktioniert das nicht. Wenn a Shippingentfernt wird, muss etwas prüfen, ob es eine andere Statuseinstellung inStockauf false gibt, sodass ein Entfernen von Shippingnicht einfach ist inStock = true. Upgradeerhöht sich pricebei jedem Anruf. Ich habe dann Konstanten für die Standardwerte hinzugefügt und eine Neuberechnung basierend auf diesen versucht.

Ich versuche keineswegs, ein Muster aufzuerlegen, ich versuche nur, eine Lösung für die oben genannten Anforderungen zu finden. Beachten Sie, dass in der Praxis Carviele Eigenschaften enthalten und es viele Zustände gibt, die auf diese Weise angewendet werden können. Ich dachte über ein paar Möglichkeiten nach:

  1. Da jeder Beobachter empfängt Car, kann er alle anderen derzeit registrierten Beobachter anzeigen und darauf basierend eine Änderung vornehmen. Ich weiß nicht, ob es klug ist, solche Beobachter zu verwickeln.
  2. Wenn ein Beobachter hinzugefügt oder entfernt wird Car, erfolgt eine Neuberechnung. Diese Neuberechnung muss jedoch für alle Beobachter durchgeführt werden, unabhängig davon, welcher gerade hinzugefügt / entfernt wurde.
  3. Haben Sie eine externe "Manager" -Klasse, die die Methoden zum Hinzufügen und Entfernen aufruft und die Neuberechnung durchführt.

Was ist ein gutes Entwurfsmuster, um das beschriebene Verhalten zu implementieren, und wie würde es funktionieren?

user1803551
quelle
1
Der Zweck der Abstraktion von State in eine eigene Klasse besteht darin, die Logik zu trennen, die nicht miteinander interagiert, und den Code zu vereinfachen. Ihre Geschäftslogik schreibt jedoch vor, dass ihre Logik verknüpft ist, und daher müssen Sie die Verknüpfung wieder herstellen, was zu diesem schrecklichen Durcheinander Gottes führt. Hier ist nicht das Beobachtermuster das Problem, sondern das Rollenmuster, das Sie mit State anwenden.
ArTs
Wie würden Sie das Problem lösen, wenn Sie es von Hand machen würden?
James Youngman
@ JamesYoungman Am Ende habe ich meine dritte Option verwendet - einen externen Manager. Die Regeln, die Sie für diesen Fall auf Papier schreiben, sind einfach, aber die Optionen, die Ihnen die Sprache bietet, um sie zu implementieren, sind in diesem Fall begrenzt . Daher die Notwendigkeit eines Entwurfsmusters. Das Nachdenken über "Wie würden Sie es von Hand machen?" Funktioniert eher für Algorithmen als für die Anwendung klarer Regeln.
user1803551
@ user1803551 Du hast gut gewählt.
Tulains Córdova
Haben Sie einen Ereignishandler für alle Ereignisse. Dieser Handler ist einfach der Einstiegspunkt für die Neuberechnung des vollständigen Status des Objekts. Es ist ein typisches Problem. Sie sehen es, wenn Sie ein Formular "von oben nach unten, von links nach rechts" bearbeiten - alles ist A-OK, aber wenn Sie dann etwas in der Mitte ändern, wird es nicht richtig neu berechnet. Wenn Sie sich jemals fragen: "Wie kann ich die Reihenfolge der Ereignisbearbeitung garantieren?", Wissen Sie jetzt, was Sie tun müssen.
Radarbob

Antworten:

1

Die Beobachter würden großartig funktionieren, wenn Sie das System anders faktorisieren. Anstatt Staaten selbst zu Beobachtern zu machen, könnten Sie zwei neue Klassen zu "Beobachtern von Zustandsänderungen" machen: Ein Beobachter würde "Preis" aktualisieren, ein anderer würde "inStock" aktualisieren. Auf diese Weise wären sie unabhängig, wenn Sie keine Preisregeln in Abhängigkeit von inStock haben oder umgekehrt, dh wenn alles berechnet werden könnte, indem Sie nur die Statusänderungen betrachten. Diese Technik wird als "Event Sourcing" bezeichnet (siehe z. B. https://ookami86.github.io/event-sourcing-in-practice/ ). Es ist ein Muster in der Programmierung, das einige bemerkenswerte Anwendungen hat.

Bei der Beantwortung einer allgemeineren Frage haben Sie manchmal wirklich Abhängigkeiten zwischen Beobachtern. Beispielsweise möchten Sie möglicherweise, dass ein Beobachter vor einem anderen reagiert. In diesem Fall ist es normalerweise möglich, eine benutzerdefinierte Implementierung der Observable-Klasse zu erstellen, um die Reihenfolge oder Abhängigkeiten zu berücksichtigen.

Battlmonstr
quelle
0

Am Ende habe ich Option 3 gewählt - mit einem externen Manager. Der Manager ist dafür verantwortlich, States zu Cars hinzuzufügen und daraus zu entfernen und die Beobachter zu benachrichtigen, wenn diese Änderungen eintreten.

Hier ist, wie ich den Code geändert habe. Ich habe das Observable/ Observerdes JDK entfernt, weil ich meine eigene Implementierung mache.

Jeder Statebehält einen Verweis auf das, auf das er Carangewendet wird.

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);
    }
}

Carbehält nur seinen Status bei (um Verwirrung zu vermeiden: Eigenschaften) und behandelt nicht das Hinzufügen und Entfernen von States:

class Car extends Observable {

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

Hier ist der Manager. Es hat die Car(beobachtbare) Aufgabe der Verwaltung seiner State(Beobachter) übernommen.

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;
    }
}

Ein paar Dinge zu beachten:

  • Alle Beobachter werden über jede Änderung informiert. Ein clevereres Ereignisverteilungsschema kann unnötige Aufrufe der Beobachtermethode eliminieren update.
  • Die Beobachter möchten möglicherweise mehr "Update" -ähnliche Methoden für verschiedene Anlässe verfügbar machen. Nur als Beispiel können sie die aktuelle updateMethode aufteilen updateOnAddund updateOnRemovewenn sie nur an einer dieser Änderungen interessiert sind. Dann würden die Methoden addStateund removeStateentsprechend aktualisiert. Zusammen mit dem vorherigen Punkt kann dieser Ansatz als robuster, erweiterbarer und flexibler Mechanismus enden.
  • Ich habe nicht angegeben, was die Anweisung zum Hinzufügen und Entfernen von States enthält und wann dies geschieht, da dies für die Frage nicht wichtig ist. Im Fall dieser Antwort ist jedoch der folgende Punkt zu berücksichtigen. Da Statejetzt Carvor dem Aufrufen der Manager-Methode mit (kein leerer Konstruktor verfügbar gemacht) erstellt werden muss, müssen die Methoden addStateund removeStatekein a nehmen Carund können es einfach lesen state.car.
  • Die Beobachter werden standardmäßig in der Reihenfolge der Registrierung auf dem Observable benachrichtigt. Eine andere Reihenfolge kann angegeben werden.
user1803551
quelle