【问题标题】:Is it better to use getters or to access private members directly?使用 getter 还是直接访问私有成员更好?
【发布时间】:2016-12-06 12:38:31
【问题描述】:

以下哪个更好?它甚至是基于意见的还是有任何相关的差异?在某些情况下可以选择其中一个吗?

public class MyClass {
    private Integer myField;

    public void setMyField(Integer myField) {
        this.myField = myField;
    }

    public Integer getMyField() {
        return myField;
    }

}

我需要一种方法来检查是否允许某些内容。拜托,我们不要谈论这个代码示例的意义。这只是一个小例子。

实施 1

public boolean isAllowed() {
    MyEnum.ALLOWED.getInt().equals(getMyField());
}

实施 2

public boolean isAllowed() {
    MyEnum.ALLOWED.getInt().equals(myField);
}

编辑: 此帖子在链接的问题中没有答案(请参阅初始帖子的 cmets)

【问题讨论】:

  • 就含义而言没有区别。这更多的是一个观点问题。我会选择实施 1,因为您一定会获得想要测试的值,例如,如果您有任何类型的转码
  • @RafiduzzamanSonnet 关于使用 其他类的 getter 和 setter 的公认和投票最多的答案。这里我们有一个在实现类本身中使用值的例子,已经通过定义知道它的存在和实现。
  • @RealSkeptic 这是同一个概念。否则就不需要 getter/setter 或 public 修饰符
  • @MuratK。对 getter/setter 的需求是针对其他类的,而这种需求是无关紧要的。这里的问题是是否将该 getter/setter 也用于 internal 用途或直接使用该字段。

标签: java oop coding-style


【解决方案1】:

以下哪个更好?它甚至是基于意见还是 有什么相关的区别吗?可以偏爱其中的一个吗 一些场景?

我认为这是一个良好实践的问题。区别在于代码的可读性。

作为一般规则,如果不需要,您应该避免间接使用。 MyClass 的当前实例具有这些字段之一中的信息以实现操作。它不需要对自己隐藏其内部状态。
因此,在内部,MyClass 没有任何有价值的理由支持使用 getMyField() 而不是直接使用 myField 字段。
getMyField() 访问器更适合类的客户端使用。
所以我认为在你的示例代码中无论如何都更好:

public boolean isAllowed() {
    MyEnum.ALLOWED.getInt().equals(myField);
}

编辑
除了可读性之外,这里还有一个示例为什么您没有兴趣将内部状态耦合到公共 getter。
假设在开发阶段,您从类中删除了 public getMyField() 方法,因为类的客户不再需要或不再需要,如果 isAllowed() 在其实现中依赖于 getMyField(),它将被破坏,您应该替换myField.

【讨论】:

  • 这个答案有点误导,因为这个例子过于简单并且没有语义意义。 getMyField 名称不传达任何意图或含义。该方法被命名为“getSecurityLevel”,它会更好地传达其意图。实际上,您永远不会删除此方法,因为它会改变程序的语义。相反,您将更改 getter 的实现以更改 isAllowed 的行为。这里要考虑的主要是 OCP。
  • 您最初的解释也是着眼于实现代码而不是其意图。例如。删除访问器方法会破坏调用者,这意味着您专注于它是如何拼凑在一起的,而不是专注于您希望引发的行为。它忽略了封装的好处,反而与这种做法背道而驰,暗示封装适得其反。
  • 我同意你的看法。在某些情况下,必须使用 OCP 以及更一般的抽象级别,因为我们需要扩展初始行为。但是在没有预见到并且几乎不可能发生变化的情况下,我认为这简直是矫枉过正。这与在任何类前面创建接口而类的值为零来拥有接口是一样的。有意义的代码是揭示其意图的代码。因此,我们应该只在需要时才创建抽象。
  • 否则,这种意图会产生误导,因为我们想知道为什么我们在代码中有这种抽象,而我们注意到这不是必需的。我很务实。代码存在,重构成为应用程序生命的一部分。如果需求完全改变,我们现在应该处理一个抽象级别,重构是我遵循的方式,单元测试是我的盾
  • 两件事。我在下面的回答中解决了过度简化与过度杀伤的问题,同时指出,如果没有个人经验,很难知道解耦何时重要。此外,您的解释提到了“一个为什么您没有兴趣将内部状态与公共吸气剂耦合的示例”。并继续建议直接访问作为优势。解耦绝不应该被描述为对设计的损害。整个部分都具有误导性,并掩盖了脱钩的任何好处。虽然我理解你的观点,但它看起来像好的凝聚力是一件坏事。
【解决方案2】:

我的回答不会提供最丰富的信息,但它将来自处理这种模式的直接经验。在设计对象时,通常倾向于直接访问成员字段而不是依赖访问器。这种愿望源于希望简化对象并避免从简单返回值的方法中添加混乱。以您的示例进一步添加上下文和含义:

public class MyClassmate {
    private Integer age;

    public MyClassmate(Integer age) {
        this.age = age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getAge() {
        return age;
    }

}

这里的年龄是一个简单的数字,似乎没有必要在它周围添加 getter/setter。如果我们添加以下方法,您会很想直接访问该字段,因为行为没有变化:

public Integer calculateScore() {
    if(age > 21) return 100 - getNumberOfIncorrectAnswers();
    //Add 10% before for younger students
    else return (100 - getNumberOfIncorrectAnswers()) + 10;
}

然后,您的对象可能会使用依赖于您继续直接使用它的年龄字段的方法来增加新功能。稍后,您可能会改变年龄的产生方式并从网络中提取价值。您可能不想在构造函数中包含网络逻辑,因为它是一项昂贵的操作,只能在需要时触发。 calculateScore() 方法可以建立网络连接并发现年龄,但所有其他依赖年龄的方法也可以。但是如果 calculateScore 如下所示呢?:

public Integer calculateScore() {
    if(getAge() > 21) return 100 - getNumberOfIncorrectAnswers();
    //Add 10% before for younger students
    else return (100 - getNumberOfIncorrectAnswers()) + 10;
}

然后,您可以在不触及calculateScore() 方法的情况下更改对象获取年龄的方式来增强对象。这意味着您的方法遵循开放封闭原则 (OCP)。它对增强开放但对更改关闭,或者您不必更改方法源即可更改它的年龄。

根据您的应用程序和对象模型的复杂性,有时可能仍然存在封装访问过度的情况,但即使在这些情况下,最好了解直接字段访问的权衡,而且这些更简单的情况大多很少见。

一般而言,您应该了解封装的需求几乎不会立即发生。随着对象的增长,它会随着时间的推移而出现,如果对象从一开始就没有设置封装,那么将其逐步引入会更加昂贵。这就是使这种方法如此难以理解的原因。 感觉为什么需要封装需要经验(即,在几年内多次进行典型的过度简化和痛苦)。这不是你可以用眼睛观察和发现的东西。

也就是说,与现在 IDE 功能不全的时候相比,这曾经是一个更大的问题。今天,您可以在某些 IDE(如 IntelliJ)中使用内置的 encapsulate fields 重构来根据需要引入该模式。即使使用现代 IDE,从一开始就进行封装仍然是有利的。

【讨论】:

  • 有趣的反例。年龄对于这个例子来说可能是一个过于简单的属性,但仍然是一个很好的例子。
  • 是的,我在打字时编写了示例。不是最好的解释方式,而是向原始代码引入含义和上下文的粗略方式,否则这一切都开始像一系列机械步骤一样读起来,而不是讲述一个故事。
  • @Cliff:在您的示例中,您正在利用 getter。如果要从网络中检索年龄,则应该使用汇编程序或工厂来创建MyClassmate。我认为逻辑不应该直接进入MyClassmate。否则它不再是吸气剂,而是computeAge()-method。
  • @Chris311 此示例并非旨在全面演示如何进行正确的网络连接。我确实理解它是人为的,而不是网络代码的最佳说明。相反,这里的目标是强调适当封装在引入变更方面的优势。关键是您可以在不更改其代码的情况下增强方法的行为。当您使用直接字段访问时,这变得不切实际。
  • 没错,我真的很喜欢你的回答。我投票赞成,尽管我仍然采用“直接使用成员”的方法。
【解决方案3】:

我建议使用 getter,因为在某些情况下,它可以有一些额外的逻辑(如格式化、检查空值等)。因此,在使用字段本身时,您可能会丢失一些逻辑。

【讨论】:

  • 在 getter-setter 方法中添加逻辑不是一种不好的做法吗?
  • Getter 应该没有逻辑。
  • @AlexErohin 和 Chris311 - 当然逻辑在 getter 中是允许的。否则,“隐藏实施更改”的全部意义将丢失。假设一个字段现在是int。您决定将实现更改为枚举。 getter 的合同保持不变,因为它已被其他类发布和使用。因此,您可以使用return x.getIntValue() 之类的东西而不是return x; - 这是getter 中的逻辑,而且非常好。
【解决方案4】:

为了保持良好的封装性,首先要考虑的是要在类之外公开哪些方法,例如,如果在这里,例如,您将只使用允许的方法,我只会公开方法,并在构造函数中定义字段,如果该字段适合更改,那么您将需要 getter/setter,但始终取决于您想从类中提供什么。并尽可能多地封装。

public class MyClass {
    private Integer myField;

    MyClass(Integer myField){
        this.myField = myField;
    }

    //Only this method is offered nobody need to know you use myField to say true/false
    public boolean isAllowed() {
        MyEnum.ALLOWED.equals(myField);
    }

}

【讨论】:

  • 让我们假设 getter 和 setter 是从类外部调用的。
  • 然后我会添加 getter/setter 并保持相同的字段访问权限
【解决方案5】:

在我的软件工程课程中,我被告知要实现“秘密原则”。因此,您应该始终使用 getter 和 setter 例程。这样可以确保没有人会意外访问成员变量。

奇怪的函数或对象可能永远看不到甚至改变成员变量,除非你通过 setter 和 getter 明确告诉他们这样做。

【讨论】:

  • 人们可以使用 getter 和 setter “意外地”访问变量。那你的意思是什么?
  • 意外访问“myField”比访问“getMyField()”更容易。
【解决方案6】:

由于您的属性是私有的,您只能在其他类中使用 getter 或 setter 方法安全地访问它。所以我会说最好的实现是遵循封装原则的实现,即使用getter而不是直接访问的实现。 这也将防止数据泄露。

https://www.tutorialspoint.com/java/java_encapsulation.htm

【讨论】:

  • 我的字段被封装了。这篇文章没有谈到访问同一个类中的变量。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-09-25
  • 2020-09-21
  • 1970-01-01
  • 2014-08-21
  • 1970-01-01
  • 2011-01-18
  • 2012-12-20
相关资源
最近更新 更多