получатели и установщики, выполняющие дополнительную логику

#java #pojo

#java #pojo

Вопрос:

У меня есть класс Java, который представляет корреляцию между двумя элементами (типичный POJO):

 public class Correlation {

    private final String a;
    private final String b;

    private double correlation;

    public Correlation(String a, String b) {
        this.a = a;
        this.b = b;
    }

    public double getCorrelation() {
        return correlation;
    }

    public void setCorrelation(double correlation) {
        this.correlation = correlation;
    }

}
 

Чтобы следовать правильной логике корреляции, если a равно b, тогда значение корреляции всегда должно быть 1.
Я мог бы добавить логику, изменяющую метод получения (игнорируя факт возможного нулевого значения для a):

 public double getCorrelation() {
    if (a.equals(b)) {
        return 1D;
    } else {
        return correlation;
    }
}
 

Что меня беспокоит, так это добавление этой логики в метод получения, должен ли я изменить имя метода или его документирование следует считать достаточным?

Комментарии:

1. Мне также не нравится этот подход, потому что в Kotlin мы используем синтаксис доступа к свойствам, а kotlin совместим с Java. Таким образом, люди также могут совершать ошибки, если в будущем код будет перенесен на kotlin с java

Ответ №1:

Еще в первые дни Java пары getter / setter использовались для идентификации свойств компонентов именно с целью обеспечения возможности определения концептуальных атрибутов, реализуемых посредством вычислений, а не простой переменной-члена.

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

С другой стороны, полезно отличать объекты, которые выполняют вычисления, от объектов, которые просто переносят данные; Я думаю, вам следует решить, какой тип класса вы хотите реализовать. На вашем месте я, вероятно, сделал бы корреляцию дополнительным аргументом конструктора и проверил бы его достоверность там, а не в вашем геттере. Ваш Correlation не может быть вычислительным объектом, поскольку у него недостаточно информации для выполнения каких-либо вычислений.

Комментарии:

1. 1; это вся правда! Важно различать объекты реального объекта и немые объекты передачи данных.

2. Я согласен, за исключением последнего предложения. Я думаю, что корреляция может быть вычислительным объектом, поскольку она (по крайней мере) может вычислять корреляцию, когда a равно b .

3. @Jala: Корреляция — это не то, что равно 1, когда a и b равны и не определены в противном случае: одно дело проверять аргументы, а другое — вычислять функцию указанных аргументов.

4. Даже если логика была определена в другом классе, вы бы добавили ограничение значения для метода setter?

5. Если подумать, вероятно, нет. Эта логика относится к тому месту, где фактически вычисляется корреляция, и этот тест является очень частичным и, вероятно, избыточным. С другой стороны, если вам это нужно, это лучшее место для его размещения 😉

Ответ №2:

Побочные эффекты в геттерах и сеттерах, как правило, не очень хорошая идея, поскольку обычно они не ожидаются и могут привести к сложным ошибкам. Я бы предложил создать метод «correlate ()» или что-то еще, что в данном случае не является геттером.

Надеюсь, это поможет.

Комментарии:

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

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

3. я согласен, что помещение логики в геттеры / сеттеры обычно не является отличной идеей, но иногда вам могут понадобиться некоторые побочные эффекты — например, пользовательский объект может зашифровать / расшифровать пароль в установщике / получателе

4. @aishwarya согласился с хорошей точкой зрения, чтобы прояснить мой ответ, побочные эффекты или операции, выполняемые в сеттерах / геттерах, должны быть сопряжены, чтобы использовать ваш пример… если вы зашифруете значение в set, вы расшифруете его при get.

Ответ №3:

Имеет больше смысла применять значение во setCorrelation(...) время . Например,

 public void setCorrelation(double correlation) {
  if (a.equals(b)) {
    if (Math.abs(correlation - 1D) > EPSILON) {
      throw new InconsistentException(...);
    }
    this.correlation = 1D;
  } else {
    this.correlation= correlation;
  }
}
 

Я бы также подумал о том, чтобы сделать свойство correlation равным нулю, где a null указывает, что корреляция еще не установлена.

Ответ №4:

Учитывая, что корреляция является каким-то образом / иногда «производным» значением от a и b (т. Е. Оно равно 1, если a равно b, но оно может быть вычислено каким-то оригинальным способом в зависимости от (a, b), хорошим вариантом может быть вычисление корреляции в конструкторе и выдача исключения IllegalArgumentException в setCorrelationесли vaule нарушает внутреннюю логику объекта:

 public class Correlation {

    private final String a;
    private final String b;

    private double correlation;

    public Correlation(String a, String b) {
        this.a = a;
        this.b = b;
        calculateCorrelation();
    }

    protected calculateCorrelation() { 
        // open to more complex correlation calculations depending on the input,
        // overriding by subclasses, etc.
        if (a.equals(b)) {
            this.correlation = 1D;
        } else {
            this.correlation = 0;
        }
    }

    public double getCorrelation() {
        return correlation;
    }

    public void setCorrelation(double correlation) throws IllegalArgumentException {
        if (a.equals(b) amp;amp; correlation != 1D) {
            throw new IllegalArgumentException("Correlation must be 1 if a equals b");
        }

        this.correlation = correlation;
    }
}
 

Следуя этой схеме, вы также можете «генерировать» свой класс корреляции.

Комментарии:

1. это хороший подход, но логика «a.равно (b) тогда 1» повторяется в классе дважды.

Ответ №5:

Я бы сказал, используйте что-то вроде GetValue()

Комментарии:

1. В конце концов, это «get» часть имени, которая меня убивает.

Ответ №6:

Если бы я использовал ваш класс, я бы ожидал, что getCorrelation() вернет корреляцию. На самом деле, я бы, вероятно, перепроектировал класс, чтобы иметь статический метод correlateElements(строка a, строка b), который возвращает double .

Ответ №7:

В случаях, когда a != b, как вычисляется корреляция?

Если корреляция вычисляется в основном из a и b, setCorrelation() следует удалить. Точка. Корреляция должна быть вычислена в конструкторе или в методе getCorrelation(). Одним из принципов ООП является группирование связанных данных и логики, поэтому вычисление корреляции в идеале должно выполняться в классе, который вы умно назвали Correlation . Если вычисление чрезвычайно сложное, оно может быть в другом месте (например, DIP), но вызов должен быть выполнен из Correlation .

Если корреляция не вычисляется из a и b, я действительно не понимаю класс, поэтому «это зависит». Если a равно b, и кто-то вызывает setCorrelation(0.0), заключается ли контракт в том, чтобы спокойно игнорировать вызов и оставить корреляцию на 1.0 или выдать исключение? И, если я пишу вызывающий код, я нахожусь в затруднительном положении, поскольку я не знаю, что произойдет, если я попытаюсь установить корреляцию (0.0), потому что я понятия не имею, что такое a и b, или же везде, где я делаю вызов, я вынужден идти if (getA().equals(getB())) и т. Д. … Или перехватыватьисключение и делать, э, что??? Что нарушает DRY и именно поэтому в ООП говорится, что логика и данные должны быть сгруппированы вместе в классе.