Универсальная платформа Java для управления двунаправленными ассоциациями и обратными обновлениями

Я искал общий способ работы с двунаправленными ассоциациями и способ обработки обратных обновлений в написанном вручную Java-коде.

Для тех, кто не знает, о чем я говорю, вот пример. Ниже приведены мои текущие результаты (неудовлетворительные) решения.

public class A {
    public B getB();
    public void setB(B b);
}

public class B {
    public List<A> getAs();
}

Теперь, при обновлении любого конца ассоциации, чтобы поддерживать согласованность, другой конец также должен быть обновлен. Либо каждый раз вручную

a.setB(b);
b.getA().add(a);

или поместив соответствующий код в установщик / получатель и использовать пользовательскую реализацию List.

Я нашел устаревший, не поддерживаемый проект, чьи зависимости больше не доступны ( https://e-nspire-gemini.dev.java.net/). Он решает проблему с помощью аннотаций, которые используются для автоматического ввода необходимого кода.

Кто-нибудь знает другую структуру, которая занимается этим в общем, ненавязчивом виде аля Близнецы?

чао, Эльмар

4 ответа

Решение

Google коллекции (из внутреннего кода Google) - http://code.google.com/p/google-collections/ совместим с Java Generics (не только совместим, очень хорошо использует дженерики)

Класс BiMap - http://google-collections.googlecode.com/svn/trunk/javadoc/index.html?http://google-collections.googlecode.com/svn/trunk/javadoc/com/google/common/collect/package-summary.html допускает двунаправленные ассоциации.

Ожидается, что некоторые из этих классов войдут в JDK 7.

Чтобы иметь смысл, эти calsses будут пэрами. Я предлагаю частно-пакетный механизм (в отсутствие друга) для обеспечения согласованности.

public final class A {
    private B b;
    public B getB() {
        return b;
    }
    public void setB(final B b) {
        if (b == this.b) {
            // Important!!
            return;
        }
        // Be a member of both Bs (hence check in getAs).
        if (b != null) {
            b.addA(this);
        }
        // Atomic commit to change.
        this.b = b;
        // Remove from old B.
        if (this.b != null) {
            this.b.removeA(this);
        }
    }
}

public final class B {
    private final List<A> as;
    /* pp */ void addA(A a) {
        if (a == null) {
            throw new NullPointerException();
        }
        // LinkedHashSet may be better under more demanding usage patterns.
        if (!as.contains(a)) {
            as.add(a);
        }
    }
    /* pp */ void removeA(A a) {
        if (a == null) {
            throw new NullPointerException();
        }
        as.removeA(a);
    }
    public List<A> getAs() {
        // Copy only those that really are associated with us.
        List<A> copy = new ArrayList<A>(as.size());
        for (A a : as) {
            if (a.getB() == this) {
                copy.add(a);
            }
        }
        return Collection.unmodifiableList(copy);
    }
}

(Отказ от ответственности: не проверено и даже не скомпилировано.)

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

Если вы не абстрагируете сеттеры, вам придется предоставить какой-то механизм уведомления о событиях. Если ваши объекты являются JavaBeans, то вы рассматриваете использование PropertyChangeSupport и запуск событий изменения свойств.

Если вы делаете это (или имеете какой-то другой механизм для обнаружения изменений), то Glazed Lists предоставляет ObservableElementList, который может быть легко использован для обработки синхронизации ассоциации с конца списка (т.е. добавление A в список автоматически вызывает a.setB(б)). Другое направление легко обрабатывается с помощью мониторинга изменения свойств (или эквивалентного).

Я понимаю, что это не общее решение, но кажется, что оно будет легким основанием для него.

Обратите внимание, что что-то вроде этого потребовало бы специальной реализации списка в классе B - ни в коем случае не меньше решений типа AOP, которые вы могли бы обработать в общем случае (например, используя ArrayList или что-то подобное).

Я должен также отметить, что то, что вы пытаетесь достичь, является чем-то вроде святого Грааля привязки данных. Существует несколько достойных реализаций для связывания на уровне полей (например, геттеры и сеттеры) (см. Пример привязки JGoodies и JSR 295). Существует также одна действительно хорошая реализация для связывания типов списков (Glazed Lists, упомянутые выше). Мы используем оба метода в сочетании друг с другом почти во всех наших приложениях, но никогда не пытались стать настолько абстрактными, как то, о чем вы спрашиваете.

Если бы я проектировал это, я бы посмотрел на что-то вроде этого:

AssociationBuilder.createAssociation(A a, Connector< A> ca, B b,  Connector< B> cb, Synchronizer< A,B> sync)

Соединитель - это интерфейс, который позволяет использовать единый интерфейс для различных типов уведомлений об изменениях. Synchronizer - это интерфейс, который вызывается для того, чтобы убедиться, что оба объекта синхронизированы при каждом изменении одного из них.

sync(ChangeInfo info, A a, B b) // make sure that b reflects current state of a and vice-versa.  

ChangeInfo предоставляет данные о том, какой член изменился, и какие изменения были на самом деле. Мы. Если вы пытаетесь действительно сохранить этот универсальный шаблон, то вам в значительной степени придется реализовать его до пользователя фреймворка.

Учитывая вышесказанное, можно иметь несколько предопределенных соединителей и синхронизаторов, которые соответствуют различным критериям привязки.

Интересно, что приведенная выше сигнатура метода очень похожа на вызов метода JSR 295 createAutoBinding(). Объекты свойств являются эквивалентом Connector. JSR 295 не имеет синхронизатора (вместо этого у них есть стратегия привязки, заданная как ENUM - плюс JSR 295 работает только с привязкой свойства-> свойства, пытаясь привязать значение поля одного объекта к членству списка этого объекта в другом объекте даже не на столе для них).

Спасибо за все предложения. Но никто не подошел близко к тому, что я искал, я, вероятно, сформулировал вопрос неправильно.

Я искал замену Близнецам, чтобы найти способ справиться с этим ненавязчивым образом, не загрязняя код бесконечными проверками и специальными реализациями List. Это, конечно, требует подхода, основанного на АОП, как предлагает Кевин.

Когда я немного осмотрелся, я обнаружил в cnet пакет gemini, содержащий все источники и зависимости от источников. Отсутствующие источники для зависимостей были единственной проблемой, которая помешала мне использовать его. Так как теперь все источники доступны, ошибки могут быть исправлены. В случае, если кто-то ищет это: http://www.download.com/Gemini/3000-2413_4-10440077.html

Другие вопросы по тегам