AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / coding / Perguntas / 79137339
Accepted
user20726076
user20726076
Asked: 2024-10-29 21:02:25 +0800 CST2024-10-29 21:02:25 +0800 CST 2024-10-29 21:02:25 +0800 CST

Ligações bidirecionais javafx fora de sincronia

  • 772

Eu estava escrevendo uma classe em javafx onde eu tinha duas propriedades que eram vinculadas bidirecionalmente e estava testando alguns casos extremos. Descobri que se você alterasse o valor de uma das propriedades de dentro de um ouvinte de invalidação, isso fazia com que as propriedades ficassem fora de sincronia. Aqui está um pequeno exemplo:

static class A {
    IntegerProperty x = new SimpleIntegerProperty(this, "x", 0);
    IntegerProperty y = new SimpleIntegerProperty(this, "y", 0);
    A() {
        x.bindBidirectional(y);
    }
}

static void testA() {
    A a = new A();
    a.x.addListener( o -> { //InvalidationListener
        if (a.x.get() < 0) a.x.set(0);
    });
    // after y is set to a negative value, x and y hold different values
    // until either y is set to a value that's >= 0 or x is set to any value
    a.y.set(-2);
    System.out.println(a.x.get());
    System.out.println(a.y.get());
}

Saída:

0
-2

Eu estava assumindo que ao usar uma ligação bidirecional, alterar uma propriedade sempre faria com que a outra fosse atualizada. Parece raro (e possivelmente imprudente) que alguém escrevesse um ouvinte de invalidação como esse, mas estou pensando defensivamente aqui. Se pelo menos uma dessas propriedades fosse exposta, não quero que seja possível quebrar nenhuma invariante da minha classe. Eu estava pensando que há três explicações possíveis aqui:

  1. O contrato em ligações bidirecionais não é que elas sempre estejam em sincronia (ou elas mantêm o mesmo valor ou são marcadas como inválidas), é apenas em uma base de melhor esforço. Assim, invariantes de classe não devem ser baseadas nesse fato.

  2. Alterar o valor dentro de um ouvinte de invalidação quebra a pré-condição de vínculos bidirecionais e deve ser evitado. Caso contrário, eles estão sempre em sincronia. Assim, você pode basear uma invariante de classe nesse fato, já que é razoável exigir que os clientes não escrevam ouvintes de invalidação assim.

  3. É um bug e eles realmente devem ser sincronizados, não importa o que aconteça. Acho que se isso fosse verdade, você poderia facilmente produzir um loop infinito de notificações de alteração (como adicionar um ouvinte de invalidação a y que sempre define o valor como < 0). Então, caberia ao cliente evitar tais casos.

Alguma dessas explicações está próxima da verdade ou estou esquecendo de algo mais aqui? Além disso, eu gostaria de saber se existe algum outro tipo de operação bind que leve esses tipos de situações em consideração.

java
  • 2 2 respostas
  • 84 Views

2 respostas

  • Voted
  1. Best Answer
    jewelsea
    2024-10-30T13:31:09+08:002024-10-30T13:31:09+08:00

    Sugiro que sua segunda teoria seja verdadeira:

    Alterar o valor dentro de um ouvinte de invalidação quebra a pré-condição de binds bidirecionais e deve ser evitado. Caso contrário, eles estão sempre em sincronia.

    Portanto, você pode basear uma invariante de classe nesse fato, já que é razoável exigir que os clientes não escrevam ouvintes de invalidação dessa forma.

    Operação de ligação bidirecional

    Para entender a operação de ligação bidirecional, você pode consultar a fonte.

    Veja, por exemplo, TypedNumberBidirectionBinding . Ele tem um sinalizador updatingque define e verifica quando uma propriedade é invalidada. Se a propriedade for atualizada novamente dentro do ouvinte de invalidação, o sinalizador de atualização será verdadeiro e a sincronização do valor da propriedade será ignorada.

    (código de invalidação simplificado para fins de exemplo).

    if (!updating) {
        updating = true;
        try {
            if (property1 == sourceProperty) {
                T newValue = property1.getValue();
                property2.setValue(newValue);
                property2.getValue();
                oldValue = newValue;
            } else {
                T newValue = (T)property2.getValue();
                property1.setValue(newValue);
                property1.getValue();
                oldValue = newValue;
            }
        } finally {
            updating = false;
        }
    }
    

    Recomendação

    O javadoc InvalidationListener afirma:

    Em geral, é considerado uma má prática modificar o valor observado neste método.

    Portanto, recomendo que você não "altere o valor de uma das propriedades de dentro de um ouvinte de invalidação".

    O caso do controle deslizante

    uma ligação bidirecional na valueProperty da classe Slider não sincronizará corretamente quando estiver fora dos limites

    Isso ocorre porque a classe Slider faz o que não é recomendado. Ela modifica o valor da propriedade value do slider em um listener de invalidação .

    @Override protected void invalidated() {
        adjustValues();
        notifyAccessibleAttributeChanged(AccessibleAttribute.VALUE);
    }
    

    A modificação é fixar o valor da alteração no intervalo do controle deslizante.

    Isso leva ao caso de uma vinculação bidirecional na valueProperty da classe Slider não sincronizar corretamente quando estiver fora dos limites.

    A documentação do controle deslizante afirma :

    Este valor deve estar sempre entre min e max, inclusive. Se ele estiver fora dos limites, seja devido à mudança de min ou max ou devido a ele mesmo ter sido alterado, então ele será fixado para permanecer sempre válido.

    Como projetar em torno disto

    Conforme sugerido nesta resposta :

    a solução foi não expor minha propriedade vinculada diretamente, mas sim usar um ReadOnlyObjectWrapperpara tornar minha propriedade somente leitura fora da minha classe.

    Como propriedades somente leitura não podem ser definidas, você não pode anexar o tipo de ouvinte de invalidação que quebra binds a elas. Você ainda pode permitir modificações, por meio de um setter personalizado ou uma segunda propriedade, se necessário.

    Em outras partes da API JavaFX, para casos um tanto semelhantes, a abordagem com conjuntos de propriedades gêmeas é usada, por exemplo, a propriedade node read/write disable e a propriedade read-only disabled , ou o uso da propriedade read-only na classe Window , como as propriedades output scale read-only e render scale read/write.

    • 6
  2. user20726076
    2024-10-30T18:04:54+08:002024-10-30T18:04:54+08:00

    Estou marcando a resposta de jewelsea como correta, ela esclarece as coisas muito bem, eu acho. Eu só queria acrescentar que para o meu caso em particular, já que eu queria que meus binds sempre se mantivessem, a solução foi não expor minha propriedade bound diretamente, mas sim usar um ReadOnlyObjectWrapperpara tornar minha propriedade somente leitura fora da minha classe. Já que propriedades somente leitura não podem ser definidas, você não pode anexar o tipo de ouvinte de invalidação que quebra binds a elas. Você ainda pode permitir modificações, por meio de um setter personalizado ou uma segunda propriedade, se necessário.

    Eu acho (com o benefício da retrospectiva) que o Slider deveria ter sido implementado usando uma abordagem similar. Por exemplo:

    public class Slider {
        private ReadOnlyDoubleWrapper value;
        private DoubleProperty requestedValue;
        private DoubleProperty min;
        private DoubleProperty max;
    
        public Slider(double minVal, double maxVal, double val) {
            min = new SimpleDoubleProperty(minVal > maxVal ? maxVal : minVal);
            max = new SimpleDoubleProperty(maxVal);
            requestedValue = new SimpleDoubleProperty(val);
            value = new ReadOnlyDoubleWrapper();
            adjustValue();
            requestedValue.addListener((obs, oldVal, newVal) -> adjustValue());
    
            // also, update min/max/value whenever the bounds change
        }
    
        // updates the value property to the closest value to requested value that's still in range
        private void adjustValue() {
            double newValue = requestedValue.get();
            if (newValue < min.get()) {
                value.set(min.get());
            } else if (newValue > max.get()) {
                value.set(max.get());
            } else {
                value.set(newValue);
            }
        }
        
        public ReadOnlyDoubleProperty valueProperty() {
            return value.getReadOnlyProperty(); // return a read-only version, that can't be modified or bound
        }
    
        // getters/setters for the other properties
    }
    

    Agora, todos os binds funcionarão corretamente para requestedValue, enquanto valuenão pode ser vinculado diretamente. Isso também torna o código mais autodocumentado - o valor solicitado não é o mesmo que o valor real. Observe que não é necessário ter um min/max solicitado, pois eles realmente não podem ser vetados, em vez disso, alterar por exemplo minpode fazer com que maxe valuetambém sejam alterados.

    • 4

relate perguntas

  • Lock Condition.notify está lançando java.lang.IllegalMonitorStateException

  • Resposta de microsserviço Muitos para Um não aparece no carteiro

  • Validação personalizada do SpringBoot Bean

  • Os soquetes Java são FIFO?

  • Por que não é possível / desencorajado definir um lado do servidor de tempo limite de solicitação?

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Vue 3: Erro na criação "Identificador esperado, mas encontrado 'import'" [duplicado]

    • 1 respostas
  • Marko Smith

    Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle?

    • 1 respostas
  • Marko Smith

    Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores?

    • 1 respostas
  • Marko Smith

    Como faço para corrigir um erro MODULE_NOT_FOUND para um módulo que não importei manualmente?

    • 6 respostas
  • Marko Smith

    `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso?

    • 3 respostas
  • Marko Smith

    Quando devo usar um std::inplace_vector em vez de um std::vector?

    • 3 respostas
  • Marko Smith

    Um programa vazio que não faz nada em C++ precisa de um heap de 204 KB, mas não em C

    • 1 respostas
  • Marko Smith

    PowerBI atualmente quebrado com BigQuery: problema de driver Simba com atualização do Windows

    • 2 respostas
  • Marko Smith

    AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos

    • 1 respostas
  • Marko Smith

    Estou tentando fazer o jogo pacman usando apenas o módulo Turtle Random e Math

    • 1 respostas
  • Martin Hope
    Aleksandr Dubinsky Por que a correspondência de padrões com o switch no InetAddress falha com 'não cobre todos os valores de entrada possíveis'? 2024-12-23 06:56:21 +0800 CST
  • Martin Hope
    Phillip Borge Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle? 2024-12-12 20:46:46 +0800 CST
  • Martin Hope
    Oodini Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores? 2024-12-12 06:27:11 +0800 CST
  • Martin Hope
    sleeptightAnsiC `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso? 2024-11-09 07:18:53 +0800 CST
  • Martin Hope
    The Mad Gamer Quando devo usar um std::inplace_vector em vez de um std::vector? 2024-10-29 23:01:00 +0800 CST
  • Martin Hope
    Chad Feller O ponto e vírgula agora é opcional em condicionais bash com [[ .. ]] na versão 5.2? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench Por que um traço duplo (--) faz com que esta cláusula MariaDB seja avaliada como verdadeira? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng Por que `dict(id=1, **{'id': 2})` às vezes gera `KeyError: 'id'` em vez de um TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos 2024-03-20 03:12:31 +0800 CST
  • Martin Hope
    MarkB Por que o GCC gera código que executa condicionalmente uma implementação SIMD? 2024-02-17 06:17:14 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve