Подходит ли схема наблюдателя, когда наблюдатели не зависят друг от друга?

9

У меня class Carесть 2 свойства: int priceи boolean inStock. Он также держит Listиз abstract class State(пустого класса). Есть 2 состояния, которые могут быть применены к автомобилю, и каждый представлен своим собственным классом: class Upgrade extends Stateи class Shipping extends State.

A Carможет содержать любое количество каждого из 2 состояний. В штатах действуют следующие правила:

  • Upgrade: добавляет 1к цене для каждого государства, примененного к автомобилю после себя.
  • Shipping: если Shippingв списке хотя бы 1 состояние, inStockустанавливается значение false.

Например, начиная с price = 1и 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

Я думал о шаблоне наблюдателя, где каждая операция добавления и удаления уведомляет наблюдателей. Я имел в виду нечто подобное, но оно не подчиняется правилам, которые я изложил:

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

Очевидно, это не работает. Когда a Shippingудаляется, что-то должно проверить, есть ли другое состояние, установленное inStockв false, поэтому удаление Shippingне может просто inStock = true. Upgradeувеличивается priceпри каждом вызове. Затем я добавил константы для значений по умолчанию и попытался пересчитать их.

Я ни в коем случае не пытаюсь навязать какой-либо шаблон, я просто пытаюсь найти решение для вышеуказанных требований. Обратите внимание, что на практике Carсодержится много свойств, и существует много состояний, которые можно применять таким образом. Я думал о нескольких способах сделать это:

  1. Поскольку каждый наблюдатель получает Car, он может просматривать всех других наблюдателей, зарегистрированных в настоящее время, и вносить в них изменения. Я не знаю, разумно ли так запутывать наблюдателей.
  2. Когда наблюдатель будет добавлен или удален Car, произойдет перерасчет. Тем не менее, этот пересчет должен быть сделан для всех наблюдателей, независимо от того, который был только что добавлен / удален.
  3. Иметь внешний класс "manager", который будет вызывать методы add и remove и выполнять пересчет.

Что такое хороший шаблон проектирования для реализации описанного поведения и как оно будет работать?

user1803551
источник
1
Смысл абстрагирования State в его собственный класс заключается в выделении логики, которая не взаимодействует друг с другом, что упрощает код. Однако ваша бизнес-логика диктует, что их логика связана, и, следовательно, вы должны вернуться обратно в ссылку, что приведет к ужасному беспорядку Бога. Проблема здесь не в паттерне наблюдателя, а в паттерне ролей, который вы применяете в State.
Артс
Как бы вы решили проблему, если бы делали это вручную?
Джеймс Янгман
@JamesYoungman Я выбрал третий вариант - внешний менеджер. Правила, которые вы пишете на бумаге для этого случая, просты, но варианты, которые язык предоставляет вам для их реализации, в этом случае ограничены . Отсюда необходимость дизайна шаблона. Размышление о том, «как бы вы сделали это вручную», больше работает для алгоритмов, чем для применения четкого набора правил.
user1803551
@ user1803551 Вы выбрали хорошо.
Тулаинс Кордова
Есть один обработчик событий для всех событий. Этот обработчик является просто точкой входа для пересчета полного состояния объекта. Это типичная проблема. Вы видите, что это проявляется при работе с формой «сверху вниз, слева направо» - все в порядке, но затем изменение чего-либо в середине не будет правильно пересчитано. Если вы когда-нибудь спросите: «Как я могу гарантировать порядок обработки событий?», Теперь вы знаете, что нужно делать.
радаробоб

Ответы:

1

Наблюдатели будут отлично работать, если вы по-разному учитываете систему. Вместо того, чтобы делать состояния самими наблюдателями, вы можете сделать 2 новых класса «наблюдателями изменений состояния»: один наблюдатель обновит «цену», другой обновит «inStock». Таким образом, они будут независимы, если у вас нет правил для цены в зависимости от inStock или наоборот, т.е. если все можно рассчитать, просто посмотрев на изменения состояния. Этот метод называется «источником событий» (например, см. Https://ookami86.github.io/event-sourcing-in-practice/ ). Это паттерн в программировании, который имеет некоторые известные приложения.

Отвечая на более общий вопрос, иногда у вас действительно есть зависимости между наблюдателями. Например, вы можете захотеть, чтобы один наблюдатель реагировал раньше другого. В таком случае обычно можно создать собственную реализацию класса Observable для работы с порядком или зависимостями.

battlmonstr
источник
0

В итоге я выбрал вариант 3 - использование внешнего менеджера. Менеджер отвечает за добавление и удаление States из Cars, а также за уведомление наблюдателей, когда происходят эти изменения.

Вот как я изменил код. Я удалил Observable/ Observerиз JDK, потому что я делаю свою собственную реализацию.

Каждый Stateхранит ссылку на Carсвою заявку.

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

Carтолько сохраняет свое состояние (чтобы избежать путаницы: свойства) и не обрабатывает добавление и удаление States:

class Car extends Observable {

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

Вот менеджер. Он обогнал Cars (наблюдаемую) работу по управлению своими State(наблюдателями).

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

Несколько вещей, которые нужно иметь в виду:

  • Все наблюдатели уведомляются о каждом изменении. Более умная схема распределения событий может устранить ненужные вызовы updateметода наблюдателей .
  • Наблюдатели, возможно, захотят представить больше «обновляющих» методов для разных случаев. Например, они могут разделить текущий updateметод на updateOnAddи, updateOnRemoveесли они заинтересованы только в одном из этих изменений. Тогда addStateи removeStateметоды будут обновлены соответствующим образом . Наряду с предыдущим пунктом этот подход может в конечном итоге стать надежным, расширяемым и гибким механизмом.
  • Я не уточнил, что дает инструкция по добавлению и удалению States и когда это происходит, поскольку это не важно для вопроса. Тем не менее, в случае этого ответа, есть следующий момент для рассмотрения. Поскольку в Stateнастоящее время должен быть создан с его Car(не пустой конструктор подвергается) до вызова метода менеджера, то addStateи removeStateметоды не должны принимать Carи может просто прочитать его из state.car.
  • Наблюдатели уведомляются в порядке регистрации на наблюдаемые по умолчанию. Можно указать другой порядок.
user1803551
источник