¿Es adecuado el patrón de observador cuando los observadores no son independientes entre sí?

9

Tengo un class Carque tiene 2 propiedades: int pricey boolean inStock. También contiene un Listde abstract class State(clase vacía). Hay 2 estados que se pueden aplicar en el automóvil y cada uno está representado por su propia clase: class Upgrade extends Statey class Shipping extends State.

A Carpuede contener cualquier número de cada uno de los 2 estados. Los estados tienen las siguientes reglas:

  • Upgrade: se suma 1al precio de cada estado aplicado al automóvil después de sí mismo.
  • Shipping: si hay al menos 1 Shippingestado en la lista, inStockse establece en false.

Por ejemplo, comenzando con price = 1y 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

Estaba pensando en el patrón de observador donde cada operación de agregar y quitar notifica a los observadores. Tenía algo como esto en mente, pero no obedece las reglas que planteé:

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

Obviamente, esto no funciona. Cuando Shippingse elimina a, algo tiene que verificar si hay otra configuración de estado inStocken falso, por lo que la eliminación de Shippingno puede simplemente inStock = true. Upgradeaumenta priceen cada llamada. Luego agregué constantes para los valores predeterminados e intenté un recálculo basado en esos.

De ninguna manera estoy tratando de imponer ningún patrón, solo estoy tratando de encontrar una solución para los requisitos anteriores. Tenga en cuenta que en la práctica Carcontiene muchas propiedades y hay muchos estados que se pueden aplicar de esta manera. Pensé en algunas maneras de hacer esto:

  1. Como cada observador recibe Car, puede observar a todos los otros observadores actualmente registrados y hacer un cambio basado en eso. No sé si es inteligente enredar a observadores como este.
  2. Cuando se agrega o elimina un observador Car, habrá un nuevo cálculo. Sin embargo, este recálculo deberá realizarse en todos los observadores, independientemente del que se acaba de agregar / eliminar.
  3. Tenga una clase de "administrador" externa que llamará a los métodos de agregar y quitar y hará el recálculo.

¿Cuál es un buen patrón de diseño para implementar el comportamiento descrito y cómo funcionaría?

usuario1803551
fuente
1
El punto de abstraer State en su propia clase es separar Logic que no interactúa entre sí, simplificando el código. Sin embargo, la lógica de su negocio dicta que su Lógica está vinculada y, por lo tanto, debe volver a conectar el enlace, lo que resulta en este horrible desastre. No es el patrón de observación el problema aquí, es el patrón de rol que está aplicando con State.
ArTs
¿Cómo resolverías el problema si lo hicieras a mano?
James Youngman
@JamesYoungman Terminé usando mi tercera opción: un administrador externo. Las reglas que escribe en papel para este caso son simples, pero las opciones que el lenguaje le brinda para implementarlas son limitadas en este caso . De ahí la necesidad de un patrón de diseño. Pensar en "cómo lo harías a mano" funciona más para los algoritmos que para aplicar un conjunto claro de reglas.
user1803551
@ user1803551 Elegiste bien.
Tulains Córdova
Tener un controlador de eventos para todos los eventos. Este controlador es simplemente el punto de entrada para volver a calcular el estado completo del objeto. Es un problema arquetípico. Se ve manifiesto cuando se trabaja un formulario "de arriba abajo, de izquierda a derecha": todo está bien, pero cambiar algo en el medio no se vuelve a calcular correctamente. Si alguna vez termina preguntando "¿cómo puedo garantizar la orden de manejo de eventos?", Ahora sabe lo que tiene que hacer.
radarbob

Respuestas:

1

Los observadores funcionarían muy bien si factorizaras el sistema de manera diferente. En lugar de hacer que los estados sean observadores, puede hacer que 2 nuevas clases sean "observadores de cambio de estado": un observador actualizará "precio", otro actualizará "inStock". De esta manera, serían independientes si no tiene reglas de precio que dependan de inStock o viceversa, es decir, si todo pudiera calcularse simplemente mirando los cambios de estado. Esta técnica se denomina "abastecimiento de eventos" (por ejemplo, consulte - https://ookami86.github.io/event-sourcing-in-practice/ ). Es un patrón en la programación que tiene algunas aplicaciones notables.

Respondiendo a una pregunta más general, a veces realmente tienes dependencias entre los observadores. Por ejemplo, es posible que desee que un observador reaccione antes que otro. En tal caso, generalmente es posible hacer una implementación personalizada de la clase Observable para tratar con pedidos o dependencias.

battlmonstr
fuente
0

Terminé con la opción 3: usar un administrador externo. El gerente es responsable de agregar y eliminar States de Cars y de notificar a los observadores cuando ocurren estos cambios.

Así es como modifiqué el código. Eliminé el Observable/ Observerdel JDK porque estoy haciendo mi propia implementación.

Cada uno Statemantiene una referencia a Carsu aplicada.

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

Carsolo mantiene su estado (para evitar confusiones: propiedades) y no maneja agregar y eliminar States:

class Car extends Observable {

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

Aquí está el gerente. Ha superado Carel trabajo de s (el observable) de administrar sus States (observadores).

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

Un par de cosas a tener en cuenta:

  • Todos los observadores son notificados sobre cada cambio. Un esquema de distribución de eventos más inteligente puede eliminar las llamadas innecesarias al updatemétodo de los observadores .
  • Los observadores pueden querer exponer más métodos de "actualización" para diferentes ocasiones. Solo como ejemplo, pueden dividir el updatemétodo actual en updateOnAddy updateOnRemovesi están interesados ​​solo en uno de estos cambios. Luego, los métodos addStatey removeStatese actualizarían en consecuencia. Junto con el punto anterior, este enfoque puede terminar como un mecanismo robusto, extensible y flexible.
  • No especifiqué qué da las instrucciones para agregar y quitar Statesy cuándo sucede eso, ya que no es importante para la pregunta. Sin embargo, en el caso de esta respuesta, hay que considerar el siguiente punto. Como Stateahora debe crearse con su Car(sin un constructor vacío expuesto) antes de llamar al método del administrador, los métodos addStatey removeStateno necesitan tomar un Cary solo pueden leerlo state.car.
  • Los observadores son notificados por orden de registro en el observable por defecto. Se puede especificar un orden diferente.
usuario1803551
fuente