Обеспечение ограничений UML в Java?

Скажем, дизайн моего приложения таков, что есть два класса: Person и Company. Также предположим, что я определил UML-диаграмму, определяющую отношение «один ко многим» между Person и Company: каждый объект Person должен принадлежать ровно одному Company, а Company может содержать множество Person.

Каковы некоторые рекомендации по обеспечению того, чтобы это простое ограничение всегда выполнялось, т. е. чтобы никогда не было момента времени, когда объект Person содержался более чем в одном Company? Я думал о трех подходах:

  1. Совершенный код без ошибок, который никогда не нарушает мои ограничения. Однако, будучи реалистом, я знаю, что это сложно или невозможно, тем более, что количество ограничений растет.

  2. Реализуйте метод ensureCorrectness(), который вручную проверяет каждое ограничение. В этом примере он перебирает все Person и убеждается, что ровно один Company содержит этот Person. В противном случае он напечатает ошибку. Я бы вызывал этот метод время от времени.

  3. Используйте, например, Hibernate для определения схемы базы данных и сохранения данных в базе данных. В схеме я могу определить ограничение, соответствующее ограничениям на моей диаграмме UML. Затем всякий раз, когда я сохраняю данные в базе данных, Hibernate проверяет все ограничения и выводит ошибку, если что-то пошло не так.

Но я не могу не задаться вопросом, есть ли другой, более чистый способ сделать это в Java, который не полагается на (1) совершенство, (2) ручную проверку или (3) реляционную базу данных. Есть ли библиотека или фреймворк, которые позволили бы мне указать аннотации в коде, подобном:

@OneToMany(Person, Company)

Или, может быть, более конкретно:

@OneToMany(Person, Company.persons)

предполагая, что класс Company имеет переменную-член List<Person> persons.

И если бы я хотел убедиться, что номер социального страхования человека уникален для всех Person, я мог бы сказать:

@Unique(Person.SSN)

Есть ли что-нибудь подобное для Java?


person stepthom    schedule 07.06.2013    source источник


Ответы (4)


Отношения между Компанией и Человеком несложны. Сделать, например. список с элементами Person, содержащимися в Company. Единственное, что вы должны убедиться вручную, это то, что список не содержит несколько экземпляров одного человека.

В обратную сторону сложнее. Вы должны вручную проверить это (чтобы каждый список не содержал несколько экземпляров одного и того же человека). Но вы можете комбинировать его с предыдущим ограничением:

Длина всех списков лиц должна быть равна длине всех списков с уникальными лицами.

У меня мало знаний о базах данных, но я бы выбрал второй вариант (ensureCorrectNess), чтобы проверить ограничения вручную выше.

person Michel Keijzers    schedule 07.06.2013
comment
Итак, вы говорите, что в Java нет такой структуры или библиотеки, чтобы делать это автоматически, и что я должен просто реализовать вариант (2), который я указал в своем первоначальном вопросе? - person stepthom; 07.06.2013
comment
Я не очень хорошо знаю Java, но я не знаю такой библиотеки. - person Michel Keijzers; 07.06.2013

Я бы выбрал № 1 в сочетании с инкапсуляцией. Таким образом, объем кода, который должен быть на 100% свободен от ошибок, очень мал.

public class Company {
    private List<Person> persons = new ArrayList<>();
    private List<Person> publicPerson = Collections.unmodifiableList(persons);

    public List<Person> getPersons { return publicPersons; }

    public void addPerson(Person p) {
         ... ensure p is removed from old company
         p.setCompany(this);
         persons.add(p);
    }

}

public class Person {
    private Company company;
    /* pkg-private */ setCompany(Company company) {
        this.company = company;
    }
    public Company getCompany() {
        return company;
    }
}

Осторожно, этот код не является потокобезопасным!

Обратите внимание, что hibernate будет проверять ограничения только в том случае, если вы сохраните их в базе данных. Из-за кэширования могут появиться несоответствия.

person Cephalopod    schedule 07.06.2013
comment
Недостатком этого решения, аналогичного решению Eternay, является то, что нет возможности изолировать все ограничения в одном месте: они будут разбросаны по кодовой базе. Если бы у меня было 300 ограничений, не было бы простого способа увидеть/изменить их все в одном месте. - person stepthom; 07.06.2013
comment
Ограничения находятся в классах моделей, где они принадлежат. Вы можете взглянуть на свойства JavaFX, но с ними у вас будут некоторые накладные расходы. Это общая проблема: из-за статической природы Java вы в конечном итоге столкнетесь с манипуляциями с байт-кодом во время выполнения, если вам нужна центральная конфигурация. - person Cephalopod; 07.06.2013
comment
Я думаю, вы правы насчет ограничений, принадлежащих классам модели, но я все же думаю, что было бы проще написать что-то вроде @OneToMany(Person, Company.persons) вместо того, чтобы реализовывать код для этой проверки для меня. Может быть, я мечтатель. Кстати, я не понимаю, как свойства JavaFX могут помочь обеспечить отношения ManyToOne между объектами, но я исследую это немного глубже. - person stepthom; 07.06.2013
comment
Проблема с подходом аннотации заключается в том, что вам придется переписать все байт-коды доступа к полям либо во время компиляции, либо во время загрузки. Я не знаю, насколько мощными являются свойства jfx, но вы можете кое-что сделать с привязками. - person Cephalopod; 07.06.2013

Я бы реализовал это так на Java:

public class Company {
    Set<Person> persons;
    ...
    public void addPerson(Person person) {
        if (person.getCompany() != this) { // Avoiding an infinite loop between addPerson() and setCompany()
            person.setCompany(this);
            persons.add(person);
        }
    }
    public boolean removePerson(Person person) {
        return persons.remove(person);
    }
    ...
}

public class Person {
    Company company;
    public Person(Company company) {
        this.company = company;
    }
    public void setCompany(Company company) {
        if (this.company != null) {
            this.company.remove(this);
        }
        this.company = company;
        company.addPerson(this);
    }
    ...
}

В коде Person не может иметь более одного Company, а Company имеет список Persons. С конструктором Person(Company) Person назначена как минимум одна компания.

РЕДАКТИРОВАТЬ: Чтобы изменить Set (без дубликатов), вы должны пройти через метод addPerson(), который вызывает метод Person.setCompany(). Этот метод удалит Person из предыдущего списка Company и добавит его в новый список Company.

В setCompany() вы должны вызывать addPerson(), потому что программисты могут напрямую назначать Person Company без предварительного вызова addPerson().

person eternay    schedule 07.06.2013
comment
Но что мешает объекту Person оказаться в двух списках persons объекта Company? - person stepthom; 07.06.2013
comment
То есть, по сути, вы говорите, что в Java нет такого фреймворка или библиотеки для автоматической проверки таких ограничений, и что лучшее решение — использовать логику ограничений по всему коду? Так что, если бы у меня было 300 таких ограничений, не было бы возможности централизовать их все в одном месте? - person stepthom; 07.06.2013
comment
Я понимаю вашу проблему, но я не знаю какой-либо структуры, которая бы контролировала эти типы ограничений. Это не значит, что его не существует, так как я не знаю всех существующих фреймворков, их очень много... - person eternay; 07.06.2013

сначала вы можете очень легко настроить все это в БД, установив столбец в БД не нулевым, а идентификатор клиента уникальным. вы можете использовать триггеры до и после, чтобы убедиться, что данные удалены или добавлены в БД.

Во втором варианте вы можете установить все БД в классах Java, используя Hash Maps для хранения данных.

на мой взгляд первый вариант проще..

person royb    schedule 07.06.2013
comment
Спасибо за ваш ответ, но я специально просил решение, которое не зависит от базовой базы данных. Я ищу решение для чистой Java. - person stepthom; 07.06.2013