获取器和设置器执行额外逻辑

23

我有一个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可能为null值的事实):

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

让我困扰的是将这个逻辑添加到 getter 方法中,我应该更改方法名称还是仅仅记录文档就足够了?


我也不喜欢这种方法,因为在Kotlin中我们使用属性访问语法,而且Kotlin与Java是可互操作的。所以如果将来从Java迁移到Kotlin的代码中,人们可能也会犯错误。 - firstpostcommenter
7个回答

16

早期的Java开发中,Getter/Setter用于确定Bean的属性,以便能够定义通过计算实现而不是普通成员变量的概念属性。

不幸的是,随着时间的推移,程序员越来越依赖Getter/Setter只作为底层属性的访问器和修改器,这种趋势在引入术语“POJO”(只有Getter和可能有Setter方法的对象)后,已经正式确立。

另一方面,将执行计算的对象与仅携带数据的对象区分开来是一件好事;我想你应该决定要实现哪种类型的类。 如果换做是我,我会将关联性作为额外的构造函数参数,并在那里检查其有效性,而不是在Getter中进行检查。因为你的“Correlation”没有足够的信息来执行任何计算,所以它不能是一个计算对象。


+1;这就是全部的真相!区分真实对象和愚蠢的数据传输对象非常重要。 - home
我同意除了最后一句话。我认为相关性可以是一个计算对象,因为它(至少)可以在a等于b时计算相关性。 - jalopaba
@Jala:相关性并不是当a和b相等时为1,否则未定义的东西:一个是检查参数,另一个是计算这些参数的函数。 - Nicola Musatti
即使逻辑已在另一个类中定义,您是否会在setter方法中添加值约束? - eliocs
转念一想,也许不需要。那个逻辑应该放在实际计算相关性的地方,而这个测试既非常局限又可能是多余的。另一方面,如果你必须要它,那么把它放在那里是最好的选择;-) - Nicola Musatti

7

在getter和setter中引入副作用通常不是一个好主意,因为这通常是不被预期的,并且可能导致棘手的错误。我建议在这种情况下创建一个“correlate()”方法或其他不特别是getter的方法。

希望这能帮到你。


1
我在他的第二个getter中没有看到任何副作用。唯一的例外是,第一个getter可能会产生舍入误差。但这会使第二个getter更可取。 - S.L. Barth
同意。一般来说,最佳实践是尽可能让一个方法只做一件事,并尽量避免副作用。可以将其视为应用于方法级别的单一职责原则。 - Sam T.
我同意在getter/setter中放置逻辑通常不是一个好主意,但有时您可能需要一些副作用 - 例如,用户对象可能会在setter/getter中加密/解密密码。 - aishwarya
@aishwarya 同意,很好的观点。为了澄清我的回答,setter/getter 中执行的副作用或操作应该成对出现。以你的例子为例,如果你在设置时加密了值,则在获取时需要解密。 - cjstehno

1

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;
  }
}

我也会考虑将相关属性设为可空,其中null表示尚未设置相关性。

1

鉴于相关性有时是从a和b“派生”的值(即如果a等于b,则为1,但它可能根据(a,b)以某种原始方式计算),因此在构造函数中计算相关性并在setCorrelation中抛出IllegalArgumentException异常是一个不错的选择,如果该值违反了对象内部逻辑:

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) && correlation != 1D) {
            throw new IllegalArgumentException("Correlation must be 1 if a equals b");
        }

        this.correlation = correlation;
    }
}

按照这个方案,您还可以“泛型化”您的Correlation类。


这是一个好的方法,但类中的“a.equals(b) then 1”逻辑重复了两次。 - eliocs

0
我会建议使用类似于getValue()的东西。

最后,让我感到困扰的是名称中的“get”部分。 - eliocs

0

如果我在使用你的类,我会期望getCorrelation()返回相关性。实际上,我可能会重新设计这个类,使它有一个静态方法correlateElements(String a, String b),返回一个double类型的结果。


0
在a!= b的情况下,如何计算相关性?
如果相关性是主要从a和b计算的,那么应该删除setCorrelation()。 相关性应该在构造函数或getCorrelation()方法中计算。面向对象编程的一个原则是将相关的数据和逻辑分组,因此相关性的计算应该在您聪明地命名为“Correlation”的类中完成。如果计算非常复杂,则可能在其他地方(例如DIP),但调用应从“Correlation”进行。
如果相关性不是从a和b计算出来的,我真的不理解这个类,所以“这取决于情况”。如果a等于b,并且有人调用setCorrelation(0.0),那么合同是悄悄地忽略调用并将相关性保持在1.0,还是抛出异常?如果我正在编写调用代码,我会陷入困境,因为我不知道如果我尝试设置setCorrelation(0.0)会发生什么,因为我不知道a和b是什么,否则每次我进行调用时都被迫去if(getA().equals(getB()))等等...或者捕获异常并做些什么?这违反了DRY原则,这正是面向对象编程(OOP)要求逻辑和数据应该在一个类中组合在一起的原因。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接