Le modèle d'observation est-il approprié lorsque les observateurs ne sont pas indépendants les uns des autres?

9

J'ai un class Carqui a 2 propriétés: int priceet boolean inStock. Il contient également un Listof abstract class State(classe vide). Il y a 2 états qui peuvent être appliqués sur la voiture et chacun est représenté par sa propre classe: class Upgrade extends Stateet class Shipping extends State.

A Carpeut contenir n'importe quel nombre de chacun des 2 états. Les États ont les règles suivantes:

  • Upgrade: ajoute 1au prix de chaque état appliqué à la voiture après elle-même.
  • Shipping: s'il y a au moins 1 Shippingétat dans la liste, alors inStockest réglé sur false.

Par exemple, en commençant par price = 1et 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

Je pensais au modèle d'observateur où chaque opération d'ajout et de suppression avertit les observateurs. J'avais quelque chose comme ça en tête, mais ça n'obéit pas aux règles que j'ai posées:

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

De toute évidence, cela ne fonctionne pas. Quand a Shippingest supprimé, quelque chose doit vérifier s'il y a un autre paramètre d'état inStocksur false, donc une suppression de Shippingne peut pas simplement inStock = true. Upgradeaugmente priceà chaque appel. J'ai ensuite ajouté des constantes pour les valeurs par défaut et tenté un recalcul basé sur celles-ci.

Je n'essaie en aucun cas d'imposer un modèle, j'essaie simplement de trouver une solution pour les exigences ci-dessus. Notez qu'en pratique, il Carcontient de nombreuses propriétés et que de nombreux états peuvent être appliqués de cette manière. J'ai réfléchi à quelques façons de procéder:

  1. Étant donné que chaque observateur reçoit Car, il peut regarder tous les autres observateurs actuellement enregistrés et effectuer un changement en fonction de cela. Je ne sais pas s'il est intelligent d'enchevêtrer des observateurs comme celui-ci.
  2. Lorsqu'un observateur est ajouté ou supprimé Car, il y aura un nouveau calcul. Cependant, ce recalcul devra être effectué sur tous les observateurs indépendamment de celui qui vient d'être ajouté / supprimé.
  3. Avoir une classe "manager" externe qui appellera les méthodes add et remove et fera le recalcul.

Qu'est-ce qu'un bon modèle de conception pour implémenter le comportement décrit et comment cela fonctionnerait-il?

user1803551
la source
1
Le point d'abstraction de State dans sa propre classe est de séparer la logique qui n'interagit pas les uns avec les autres, simplifiant le code. Cependant, votre logique métier dicte que leur logique est liée, et par conséquent, vous devez vous mettre au niveau du lien, ce qui entraîne ce terrible gâchis de Dieu. Ce n'est pas le modèle d'observateur qui est le problème ici, c'est le modèle de rôle que vous appliquez à State.
ArTs
Comment résoudriez-vous le problème si vous le faisiez à la main?
James Youngman
@JamesYoungman J'ai fini par utiliser ma troisième option - un gestionnaire externe. Les règles que vous écrivez sur papier pour ce cas sont simples, mais les options que le langage vous donne pour les mettre en œuvre sont limitées dans ce cas . D'où la nécessité d'un modèle de conception. Penser à "comment feriez-vous à la main" fonctionne plus pour les algorithmes que pour appliquer un ensemble clair de règles.
user1803551
@ user1803551 Vous avez bien choisi.
Tulains Córdova
Avoir un gestionnaire d'événements pour tous les événements. Ce gestionnaire est simplement le point d'entrée pour recalculer l'état complet de l'objet. C'est un problème archtypique. Vous le voyez lorsque vous travaillez sur un formulaire "de haut en bas, de gauche à droite" - tout est A-OK, mais changer quelque chose au milieu ne recalcule pas correctement. Si vous finissez par demander "comment puis-je garantir l'ordre de traitement des événements?", Vous savez maintenant ce que vous devez faire.
radarbob

Réponses:

1

Les observateurs fonctionneraient très bien si vous factorisez le système différemment. Au lieu de faire des États eux-mêmes des observateurs, vous pourriez faire de 2 nouvelles classes des "observateurs des changements d'état": un observateur mettrait à jour "prix", un autre mettrait à jour "inStock". De cette façon, ils seraient indépendants si vous n'avez pas de règles de prix en fonction de InStock ou vice versa, c'est-à-dire si tout pouvait être calculé en regardant simplement les changements d'état. Cette technique est appelée "sourcing d'événements" (voir par exemple - https://ookami86.github.io/event-sourcing-in-practice/ ). C'est un modèle de programmation qui a des applications notables.

En réponse à une question plus générale, parfois vous avez vraiment des dépendances entre observateurs. Par exemple, vous pouvez souhaiter qu'un observateur réagisse avant un autre. Dans ce cas, il est généralement possible de faire une implémentation personnalisée de la classe Observable pour gérer l'ordre ou les dépendances.

battlmonstr
la source
0

J'ai fini par choisir l'option 3 - en utilisant un gestionnaire externe. Le gestionnaire est chargé d'ajouter et de supprimer des States de Cars et d'aviser les observateurs lorsque ces changements se produisent.

Voici comment j'ai modifié le code. J'ai supprimé le Observable/ Observerdu JDK car je fais ma propre implémentation.

Chacun Stategarde une référence à Carson appliqué.

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

Carne conserve que son état (pour éviter toute confusion: propriétés) et ne gère pas l'ajout et la suppression de States:

class Car extends Observable {

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

Voici le manager. Il a dépassé Carle travail (observable) de gestion de ses State(observateurs).

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

Quelques points à garder à l'esprit:

  • Tous les observateurs sont informés de chaque changement. Un schéma de distribution d'événements plus intelligent peut éliminer les appels inutiles à la updateméthode des observateurs .
  • Les observateurs voudront peut-être exposer davantage de méthodes de type "mise à jour" pour différentes occasions. À titre d'exemple, ils peuvent diviser la updateméthode actuelle updateOnAddet updateOnRemoves'ils ne sont intéressés que par l'une de ces modifications. Ensuite, les méthodes addStateet removeStateseraient mises à jour en conséquence. Parallèlement au point précédent, cette approche peut devenir un mécanisme robuste, extensible et flexible.
  • Je n'ai pas précisé ce qui donne l'instruction d'ajouter et de supprimer des States et quand cela se produit car ce n'est pas important pour la question. Cependant, dans le cas de cette réponse, il y a le point suivant à considérer. Comme Statemaintenant doit être créé avec son Car(aucun constructeur vide exposé) avant d'appeler la méthode du gestionnaire, les méthodes addStateet removeStaten'ont pas besoin de prendre un Caret peuvent simplement le lire state.car.
  • Les observateurs sont notifiés par ordre d'inscription sur l'observable par défaut. Un ordre différent peut être spécifié.
user1803551
la source