Il modello di osservatore è adatto quando gli osservatori non sono indipendenti l'uno dall'altro?


9

Ho un class Carche ha 2 proprietà: int pricee boolean inStock. Contiene anche un Listof abstract class State(classe vuota). Ci sono 2 stati che possono essere applicati sull'auto e ognuno è rappresentato dalla sua classe: class Upgrade extends Stateeclass Shipping extends State .

A Carpuò contenere qualsiasi numero di ciascuno dei 2 stati. Gli stati hanno le seguenti regole:

  • Upgrade: aggiunge 1al prezzo per ogni stato applicato all'auto dopo se stesso.
  • Shipping: se esiste almeno 1 Shippingstato nell'elenco, allora inStockè impostato su false.

Ad esempio, a partire da price = 1e 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

Stavo pensando al modello di osservatore in cui ogni operazione di aggiunta e rimozione notifica agli osservatori. Avevo in mente qualcosa del genere, ma non obbedisce alle regole che ho posto:

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

Ovviamente, questo non funziona. Quando Shippingviene rimosso, qualcosa deve verificare se esiste un'altra impostazione di stato inStocksu false, quindi una rimozione di Shippingnon può semplicemente inStock = true. Upgradeaumentaprice ad ogni chiamata. Ho quindi aggiunto costanti per i valori predefiniti e ho tentato un ricalcolo basato su quelli.

Non sto assolutamente cercando di imporre alcun modello, sto solo cercando di trovare una soluzione per i requisiti di cui sopra. Si noti che in pratica Carcontiene molte proprietà e ci sono molti stati che possono essere applicati in questo modo. Ho pensato ad alcuni modi per farlo:

  1. Poiché ogni osservatore riceve Car, può guardare tutti gli altri osservatori attualmente registrati e apportare una modifica in base a ciò. Non so se sia intelligente coinvolgere gli osservatori in questo modo.
  2. Quando un osservatore viene aggiunto o rimosso Car, ci sarà un ricalcolo. Tuttavia, questo ricalcolo dovrà essere fatto su tutti gli osservatori indipendentemente da quello appena aggiunto / rimosso.
  3. Avere una classe "manager" esterna che chiamerà i metodi add e remove ed eseguirà il ricalcolo.

Qual è un buon modello di progettazione per implementare il comportamento descritto e come funzionerebbe?


1
Il punto di sottrarre lo Stato alla propria classe è quello di separare la Logica che non interagisce tra loro, semplificando il codice. Tuttavia, la tua logica aziendale impone che la loro logica sia collegata, e quindi, devi ricollegarti al collegamento, risultando in questo disastro terribile di Dio. Non è il modello di osservatore il problema qui, è il modello di ruolo che si sta applicando con State.
Art

Come risolveresti il ​​problema se lo facessi a mano?
James Youngman,

@JamesYoungman Ho finito con la mia terza opzione: un gestore esterno. Le regole che scrivi su carta per questo caso sono semplici, ma le opzioni che la lingua ti offre per implementarle sono limitate in questo caso . Da qui la necessità di un modello di progettazione. Pensare a "come lo faresti a mano" funziona più per gli algoritmi che per l'applicazione di un chiaro insieme di regole.
user1803551

@ user1803551 Hai scelto bene.
Tulains Córdova,

Avere un gestore eventi per tutti gli eventi. Questo gestore è semplicemente il punto di ingresso per il ricalcolo dello stato completo dell'oggetto. È un problema archittico. Lo vedi manifestarsi quando si lavora su un modulo "dall'alto verso il basso, da sinistra a destra" - tutto è A-OK, ma poi cambiare qualcosa nel mezzo non ricalcola correttamente. Se finisci per chiedere "come posso garantire l'ordine di gestione degli eventi?", Ora sai cosa devi fare.
radarbob

Risposte:


1

Gli osservatori funzionerebbero alla grande se si considera il sistema in modo diverso. Invece di rendere gli stati stessi osservatori, si potrebbero fare 2 nuove classi per essere "osservatori del cambiamento di stato": un osservatore aggiornerebbe il "prezzo", un altro aggiornerebbe "inStock". In questo modo sarebbero indipendenti se non si hanno regole per il prezzo a seconda di inStock o viceversa, cioè se tutto potrebbe essere calcolato osservando solo i cambiamenti di stato. Questa tecnica è chiamata "sourcing di eventi" (ad esempio vedi - https://ookami86.github.io/event-sourcing-in-practice/ ). È un modello di programmazione che ha alcune applicazioni notevoli.

Rispondendo a una domanda più generale, a volte hai davvero delle dipendenze tra gli osservatori. Ad esempio, potresti desiderare che un osservatore reagisca prima di un altro. In tal caso, di solito è possibile realizzare un'implementazione personalizzata della classe Observable per gestire ordini o dipendenze.


0

Ho finito con l'opzione 3 - utilizzando un gestore esterno. Il gestore è responsabile dell'aggiunta e della rimozione di messaggi Statedi posta Carelettronica e della notifica agli osservatori quando si verificano questi cambiamenti.

Ecco come ho modificato il codice. Ho rimosso il Observable/ Observerdel JDK perché sto eseguendo la mia implementazione.

Ognuno Statemantiene un riferimento al Carsuo applicato.

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

Carmantiene solo il suo stato (per evitare confusione: proprietà) e non gestisce l'aggiunta e la rimozione di States:

class Car extends Observable {

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

Ecco il manager. Ha superato Caril lavoro (osservabile) di gestione dei suoi State(osservatori).

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

Alcune cose da tenere a mente:

  • Tutti gli osservatori vengono informati di ogni modifica. Uno schema di distribuzione degli eventi più intelligente può eliminare le chiamate non necessarie al updatemetodo degli osservatori .
  • Gli osservatori potrebbero voler esporre più metodi simili a "update" per diverse occasioni. Ad esempio, possono dividere il updatemetodo corrente in updateOnAdde updateOnRemovese sono interessati solo a una di queste modifiche. Quindi i metodi addStatee removeStateverrebbero aggiornati di conseguenza. Insieme al punto precedente questo approccio può diventare un meccanismo solido, estensibile e flessibile.
  • Non ho specificato cosa dà l'istruzione per aggiungere e rimuovere Statese quando ciò accade non è importante per la domanda. Tuttavia, nel caso di questa risposta, è necessario considerare il punto seguente. Dato che Stateora deve essere creato con il suo Car(nessun costruttore vuoto esposto) prima di chiamare il metodo del gestore, i metodi addStatee removeStatenon devono prendere un Care possono semplicemente leggerlo state.car.
  • Gli osservatori vengono notificati in ordine di registrazione sull'osservabile di default. È possibile specificare un ordine diverso.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.