Как применить инварианты экземпляра пользовательского класса в Java

Каков наилучший способ в Java обеспечить соблюдение инвариантов экземпляра класса (т. е. гарантировать, что определенные операторы верны прямо до и после вызова любого общедоступного метода)?

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

Скажем, у меня есть следующие классы:

public class Constraint
{
  private int cardinality;

  // constructor and getter/setter omitted
}

public class Edge
{
  private int minCardinality = 1;
  private int maxCardinality = Integer.MAX_VALUE;
  private Constraint constraint = null;

  // constructors, getters/setters and other methods omitted
}

и что я хотел бы применить следующие утверждения в классе Edge:

  • 0 ‹= minCardinality ‹= maxCardinality
  • если (ограничение != ноль), то (minCardinality ‹= ограничение.getCardinality() ‹= maxCardinality)

Здесь экземпляры Edge создаются парсером с установленными (обязательными) атрибутами minCardinality и maxCardinality. Атрибут ограничения (необязательный) остается пустым. Затем экземпляры передаются другому модулю (генератору тестовых кадров), который может установить или не установить ограничение с помощью метода установки.


person Dušan Rychnovský    schedule 14.06.2013    source источник


Ответы (1)


Воспользуйтесь конструктором и вставьте в него чеки; таким образом, ваш класс Edge не заботится:

@Immutable // See JSR 305
public final class Edge
{
    private final int minCardinality;
    // ...

    public static Builder newBuilder() { return new Builder(); }

    // Edge has no public constructor anymore, only this one:
    private Edge(final Builder builder)
    {
        minCardinality = builder.minCardinality;
        // etc
    }

    @NotThreadSafe // See JSR 305
    public static final class Builder
    {
        private int minCardinality;
        // ...

        private Builder() {}

        public Builder withMinCardinality(final int minCardinality)
        {
            this.minCardinality = minCardinality;
            return this;
        }
        // etc

        public Edge build()
        {
            // checks here
            return new Edge(this);
        }
    }
}

Использование:

final Edge edge = Edge.newBuilder().withMinCardinality(xxx).etc().etc().build();
person fge    schedule 14.06.2013
comment
Спасибо. Это интересное решение, но боюсь, что оно не совсем подходит для моей ситуации. Дело в том, что объект Edge не является неизменным. Он создается только с атрибутами minCardinality и maxCardinality, а атрибут ограничения устанавливается позже. Я хотел бы убедиться, что в любой момент времени (т. е. как после создания экземпляра, так и после установки ограничения) выполняются все операторы. - person Dušan Rychnovský; 14.06.2013
comment
Ну, разве вы не можете просто передавать конструктор вместо края и только .build() когда он вам действительно нужен? (изменить этот код для этого тривиально) - person fge; 14.06.2013
comment
Часть приложения, которая отвечает за установку ограничения, делает это в отношении фактической мощности отдельных ребер. Другими словами, чтобы выяснить, какое ограничение установить на ребро, сначала нужно выяснить его кардинальность. Как вы думаете, было бы уместно добавить в билдер методы для получения мощностей? - person Dušan Rychnovský; 14.06.2013
comment
Ну да, вы всегда можете сделать это. Это немного отличается от шаблона, но если вам это нужно, дерзайте ;) - person fge; 14.06.2013
comment
В этом конкретном примере, разве утверждение в конструкторе класса Edge и в методе установки ограничений не будет работать так же хорошо? И вы бы предложили шаблон построителя всякий раз, когда есть необходимость наложить инварианты на класс? - person Dušan Rychnovský; 14.06.2013
comment
Сделать такую ​​проверку в сеттере, конечно, можно, дело в том, что сам класс может быть не в том состоянии, когда он готов проверить ограничение (например, вы установили член Constraint, но не проверили мин. /макс. количество элементов). Кроме того, мне нравятся неизменяемые классы... Итак, буду ли я каждый раз предлагать конструктор? Да. Это универсальное решение? Нет ;) - person fge; 14.06.2013
comment
Разработчики добавляют много стандартного кода в простой класс. Это практика, которая не показывает правильный ответ и фактически избегает того, как учить проверку инвариантности. Код должен вызвать метод checkInvariance(), чтобы проверить соответствие параметров конструктора требованию инвариантности повторений. Если вы не проверите, вы можете передать нулевые или недопустимые параметры в Builder, что также не удастся. - person Gustavo Rodrigues; 27.02.2019