O padrão do observador é adequado quando os observadores não são independentes um do outro?

9

Eu tenho um class Carque tem 2 propriedades: int pricee boolean inStock. Ele também contém um Listde abstract class State(classe vazia). Existem 2 estados que podem ser aplicados no carro e cada um é representado por sua própria classe: class Upgrade extends Statee class Shipping extends State.

A Carpode conter qualquer número de cada um dos 2 estados. Os estados têm as seguintes regras:

  • Upgrade: aumenta 1o preço de cada estado aplicado ao carro depois dele.
  • Shipping: se houver pelo menos 1 Shippingestado na lista, inStockserá definido como false.

Por exemplo, começando com 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

Eu estava pensando no padrão de observador em que cada operação de adição e remoção notifica os observadores. Eu tinha algo em mente, mas isso não obedece às regras que eu propus:

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, isso não funciona. Quando a Shippingé removido, algo deve verificar se há outra configuração de estado inStockcomo false, portanto, a remoção de Shippingnão pode ser apenas inStock = true. Upgradeaumenta pricea cada chamada. Em seguida, adicionei constantes para os valores padrão e tentei um recálculo com base nesses valores.

Não estou tentando impor nenhum padrão, apenas estou tentando encontrar uma solução para os requisitos acima. Observe que, na prática, Carcontém muitas propriedades e há muitos estados que podem ser aplicados dessa maneira. Pensei em algumas maneiras de fazer isso:

  1. Como cada observador recebe Car, ele pode observar todos os outros observadores atualmente registrados e fazer uma alteração com base nisso. Não sei se é inteligente envolver os observadores assim.
  2. Quando um observador é adicionado ou removido Car, haverá um recálculo. No entanto, esse recálculo deverá ser feito em todos os observadores, independentemente do que acabou de ser adicionado / removido.
  3. Tenha uma classe "gerenciadora" externa que chamará os métodos de adição e remoção e fará o recálculo.

Qual é um bom padrão de design para implementar o comportamento descrito e como ele funcionaria?

user1803551
fonte
11
O objetivo de abstrair o Estado em sua própria classe é separar a lógica que não interage entre si, simplificando o código. No entanto, sua lógica de negócios determina que a lógica deles esteja vinculada e, portanto, você deve voltar ao link, resultando nessa terrível bagunça de Deus. Não é o padrão de observador que é o problema aqui, é o padrão de função que você está aplicando com o State.
ArTs 27/03
Como você resolveria o problema se estivesse fazendo isso manualmente?
James Youngman
@JamesYoungman Acabei usando minha terceira opção - um gerente externo. As regras que você escreve no papel para este caso são simples, mas as opções fornecidas pelo idioma para implementá-las são limitadas nesse caso . Daí a necessidade de um padrão de design. Pensar em "como você faria isso manualmente" funciona mais para algoritmos do que para aplicar um conjunto claro de regras.
user1803551
@ user1803551 Você escolheu bem.
Tulains Córdova
Tenha um manipulador de eventos para todos os eventos. Esse manipulador é simplesmente o ponto de entrada para recalcular o estado completo do objeto. É um problema arquíptico. Você vê isso se manifestar ao trabalhar em um formulário "de cima para baixo, da esquerda para a direita" - tudo está OK, mas alterar alguma coisa no meio não recalcula corretamente. Se você perguntar "como posso garantir a ordem de tratamento de eventos?", Agora você sabe o que precisa fazer.
Radarbob # 19/17

Respostas:

1

Os observadores funcionariam muito bem se você fatorar o sistema de maneira diferente. Em vez de tornar os próprios estados observadores, você poderia criar duas novas classes para "observadores de mudanças de estado": um observador atualizaria "preço", outro observaria "inStock". Dessa forma, eles seriam independentes se você não tiver regras de preço, dependendo do inStock ou vice-versa, ou seja, se tudo puder ser calculado apenas observando as mudanças de estado. Essa técnica é chamada "fonte de eventos" (por exemplo, consulte - https://ookami86.github.io/event-sourcing-in-practice/ ). É um padrão na programação que possui alguns aplicativos notáveis.

Respondendo a uma pergunta mais geral, às vezes você realmente tem dependências entre os observadores. Por exemplo, você pode querer que um observador reaja antes de outro. Nesse caso, geralmente é possível fazer uma implementação personalizada da classe Observable para lidar com pedidos ou dependências.

battlmonstr
fonte
0

Acabei optando pela opção 3 - usando um gerente externo. O gerente é responsável por adicionar e remover se Statede se Carnotificar os observadores quando essas mudanças ocorrerem.

Aqui está como eu modifiquei o código. Eu removi o Observable/ Observerdo JDK porque estou fazendo minha própria implementação.

Cada Stateum mantém uma referência à Carsua 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);
    }
}

Carmantém apenas seu estado (para evitar confusão: propriedades) e não controla a adição e remoção de States:

class Car extends Observable {

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

Aqui está o gerente. Ele superou Caro trabalho de (observável) de gerenciar seus 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;
    }
}

Algumas coisas a ter em mente:

  • Todos os observadores são notificados sobre cada alteração. Um esquema de distribuição de eventos mais inteligente pode eliminar chamadas desnecessárias ao updatemétodo dos observadores .
  • Os observadores podem querer expor mais métodos do tipo "atualização" para diferentes ocasiões. Apenas como exemplo, eles podem dividir o updatemétodo atual para updateOnAdde updateOnRemovese estiverem interessados ​​apenas em uma dessas alterações. Em seguida, os métodos addStatee removeStateserão atualizados de acordo. Juntamente com o ponto anterior, essa abordagem pode acabar como um mecanismo robusto, extensível e flexível.
  • Não especifiquei o que fornece instruções para adicionar e remover se, Statequando isso acontece, pois não é importante para a pergunta. No entanto, no caso desta resposta, há o seguinte ponto a considerar. Como Stateagora deve ser criado com seu Car(sem construtor vazio exposto) antes de chamar o método do gerente, os métodos addStatee removeStatenão precisam de um Care podem apenas ser lidos em state.car.
  • Os observadores são notificados em ordem de registro no observável por padrão. Uma ordem diferente pode ser especificada.
user1803551
fonte