Изящный способ обработать Циклическое Событие в Java?

я думаю это не определенная проблема мне; все, возможно, встретились с этой проблемой прежде. Для надлежащего иллюстрирования его вот, простой UI:

alt text

Как Вы видите, те два счетчика управляют единственной переменной - "A". Единственная разница - то, что они управляют им с помощью различных взглядов.

Так как значения отображения этих двух счетчиков синхронизируются, циклическое событие обнаруживается.

Если я изменю лучший счетчик, "то A" будет изменен, и нижнее значение счетчика будет также обновлено соответственно. Однако обновление нижнего вызова счетчика (такого как setValue) также инициирует другое событие, дающее лучшему счетчику команду обновить на основе нижнего значения счетчика. Таким образом создает плохой цикл, который может в конечном счете вызвать исключение StackOverFlow.

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

Теперь я хотел бы спросить, "как я могу обработать такую ситуацию изящно? (в целом, не характерный для счетчиков)"

спасибо


Обновление:

Так как у меня есть 2 ответа, предлагающие, чтобы я использовал структуру наблюдателя, я должен сказать что-то об этом.

Как то, что я сказал, это является большим, но далеким от того, чтобы быть прекрасным. Не только из-за ее свойственной сложности, но также и Ее неспособности решить проблему.

Почему? Для видения оснований необходимо понять плотное соединение Представления и Образцового Контроллера в Java Swing. Позволяет берут мой счетчик UI для примера. Предположим, что переменная A является на самом деле объектом Наблюдателя. Затем после увольнения первого события изменения состояния от лучшего счетчика The Observer "A" обновит свое значение и запустит событие PropertyChange для уведомления нижнего счетчика. Затем прибывает 2-е обновление, которое обновляет нижнее Представление счетчика. Однако изменение нижнего представления счетчика неизбежно инициировало избыточное событие, которое попытается установить значение "A" снова. Впоследствии, смертельный цикл полностью создается, и переполнение стека будет брошено.

В теории модель The Observer пытается решить прямой цикл путем представления 2 независимых путей обратной связи. Цепочечные разногласия обновления (в кодах ответа события) неявно формируют мост, соединяющий оба пути, делая цикл снова.

6
задан 11 revs, 2 users 96% 27 June 2019 в 10:03
поделиться

9 ответов

Возвращаясь к модели-представлению-контроллеру, подумайте, что это за модель и каково ваше представление.

В вашей текущей реализации у вас есть две модели (по одной для каждого элемента управления Spinner), и они синхронизируются через слой View.

Однако вам следует использовать одну и ту же модель поддержки. Для счетчика с вычтенным значением создайте прокси к исходной модели. то есть:

class ProxySpinnerModel implements SpinnerModel {
    getValue() { return originalSpinner.getValue() - 10 }
    setValue(v) { originalSpinner.setValue(v+10) }
}

spinnerA = new JSpinner()
spinnerB = new JSpinner( new ProxySpinnerModel( spinnerA.getModel() ) )

Теперь вам не нужно добавлять слушателей, поскольку они оба работают по одной и той же модели, а реализация по умолчанию (originalModel) уже имеет слушателей изменений, которые запускаются для представления.

3
ответ дан 9 December 2019 в 20:42
поделиться

Это немного сложно, но вы можете сделать A действительно наблюдаемым объектом. Оба прядильщика (или что-то еще, что должно обновляться на основе значения A ) будут тогда наблюдать A . Всякий раз, когда A изменяется, счетчики (или снова любой другой объект) обновляются, чтобы отразить новое значение A . Это отделяет логику спиннеров друг от друга. В вашем примере здесь прядильщики не должны быть связаны друг с другом, потому что они действительно не имеют ничего общего друг с другом. Вместо этого они оба должны быть просто привязаны к A и позаботиться об обновлении своего собственного представления по отдельности.

Каждый раз, когда значение в первом счетчике изменяется, вы просто обновляете значение A , чтобы оно соответствовало ему. Каждый раз, когда значение во втором счетчике изменяется, вы, конечно, должны добавить 10 к его значению, прежде чем присвоить его A .

Обновление

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

2
ответ дан 9 December 2019 в 20:42
поделиться

Например, для второго волчка вычислите A-10, а затем сравните его с текущим значением волчка. Если оно одинаково, ничего не делайте, завершая бесконечный цикл. Аналогично для первого волчка.

Я думаю, что есть еще способы обновить модель волчка так, чтобы не вызывать событие, но я их не знаю.

1
ответ дан 9 December 2019 в 20:42
поделиться

Используйте одну модель SpinnerModel для обоих JSpinners. См. следующий код: Обратите внимание, что вызов setValue() выполняется только один раз, каждый раз, когда новое значение определяется одним из JSpinners.

import java.awt.BorderLayout;

import javax.swing.*;

public class Test {
    public static void main(String[] args) {
        JFrame jf = new JFrame();
        SpinnerModel spinModel = new MySpinnerModel();
        JSpinner jspin1 = new JSpinner(spinModel);
        JSpinner jspin2 = new JSpinner(spinModel);
        jf.setLayout(new BorderLayout());
        jf.add(jspin1, BorderLayout.NORTH);
        jf.add(jspin2, BorderLayout.SOUTH);
        jf.pack();
        jf.setVisible(true);
        jf.setDefaultCloseOperation(3);
    }
}

class MySpinnerModel extends AbstractSpinnerModel {
    private int _value = 0;
    private int _min = 0;
    private int _max = 10;

    @Override
    public Object getNextValue() {
        if (_value == _max) {
            return null;
        }
        return _value + 1;
    }

    @Override
    public Object getPreviousValue() {
        if (_value == _min) {
            return null;
        }
        return _value - 1;
    }

    @Override
    public Object getValue() {
        return _value;
    }

    @Override
    public void setValue(Object value) {
        System.out.println("setValue(" + value + ")");
        if (value instanceof Integer) {
            _value = (Integer) value;
            fireStateChanged();
        }
    }
}
1
ответ дан 9 December 2019 в 20:42
поделиться

Я не очень хочу решать вашу проблему, но она мне кажется интересной. Я уже сталкивался с ней и решал ее каждый раз по-разному. Но когда я думаю о "почему?", а не о "как?", я остаюсь в недоумении.
Эта проблема существует только потому, что я использую автоматику (MVC), которая должна была мне помочь, и именно таким образом. Искусство использования компонентов делает этот автоматизм препятствием для красивого кода.
Почему set #setEvent() должен производить то же событие, что и действие GUI?

0
ответ дан 9 December 2019 в 20:42
поделиться

Хотя мое мнение тоже довольно близко к паттерну Observer, но оно немного легче !!!

Использовать A как переменную с установщиком

private Integer A;

setA(int A)
{
  this.A = A;
  refreshSpinners();
}


refreshSpinners()
{
  setSpinnerA();
  setSpinnerAMinus10();
}

setSpinnerA()
{
   // show value of A
}

setSpinnerAMinus10()
{
   // show value of A-10
}
0
ответ дан 9 December 2019 в 20:42
поделиться

Кажется, вы действительно наблюдаете не то. Из приведенного примера я предполагаю, что вы хотите обнаруживать действия пользователя с элементами управления, а не изменения самих значений. Как вы отметили, изменения в вашей модели отражаются в значениях счетчиков, и именно они формируют бесконечный цикл событий.

Однако дальнейшее погружение в реализацию пользовательского интерфейса может быть не тем ответом, который вам нужен. В этом случае я бы сказал, что лучшее, что вы можете сделать, - это либо ваше текущее защитное решение, либо лучшее извлечение логики в вашу модель (аналогично тому, что сказали Марк и Уильям). Как это можно сделать, будет зависеть от модели «реального мира», лежащей в основе конкретной реализации предоставленной головоломки.

1
ответ дан 9 December 2019 в 20:42
поделиться

Проблема решена


У меня есть много разных предложений. В частности, я хочу поблагодарить Марка У и преподобного Гонзо. Я здесь, чтобы подытожить эти идеи; это может сэкономить ваше время при просмотре большого количества текстов.

Эту проблему можно легко обойти, если аккуратно разделить представление и модель-контроллер. Мертвый цикл вызван зависимыми записями: write_1 -> write_2 -> write_1 -> ... . Интуитивно, разрыв зависимости может элегантно решить проблему.

Если мы углубимся в проблему, мы можем обнаружить, что обновление соответствующих представлений не обязательно требует внешнего вызова записи. На самом деле представление зависит только от данных, которые оно представляет. После этого мы можем переписать логику следующим образом: write_1 -> read_2 & write_2 -> read_1 .

Чтобы проиллюстрировать эту идею, давайте сравним 3 метода, упомянутых на разных плакатах: альтернативный текст http://www.freeimagehosting.net/uploads/2707f1b483.png

Как видите , только прокси-представление может решить все зависимости, поэтому это общее решение для этой проблемы.

На практике это может быть реализовано примерно так (в кодах событий-ответов):

 setValue(newValue);
 anotherSyncUI.parse();  // not anotherSyncUI.setValue() any more
 anotherSyncUI.repaint();

Больше никаких циклов. Решено.

3
ответ дан 9 December 2019 в 20:42
поделиться

Как правило, ваша модель не должна определяться вашим графическим интерфейсом. То есть SpinnerModel, поддерживающий каждый JSpinner, не должен быть вашим значением A. (Это было бы ужасно неэлегантной тесно связанной зависимостью от конкретного представления.)

Вместо этого ваше значение A должно быть либо POJO, либо свойством объекта . В этом случае вы можете добавить к нему PropertyChangeSupport. (И, по-видимому, уже сделали это в любом случае, поскольку вы хотите, чтобы ваши счетчики обновлялись автоматически, если A изменяется другими частями вашей программы).

Я понимаю, что это похоже на ответ Марка В., и вы были обеспокоены тем, что это «сложно», но PropertyChangeSupport делает почти все это за вас.

Фактически, для тривиально простых случаев вы можете просто использовать один класс, который связывает метод «setProperty» с вызовом «firePropertyChange» (а также сохраняет значение в HashMap для любых вызовов «getProperty»).

1
ответ дан 9 December 2019 в 20:42
поделиться
Другие вопросы по тегам:

Похожие вопросы: