使用公共决赛而不是私人获得者


51

我看到大多数不变的POJO都是这样写的:

public class MyObject {
    private final String foo;
    private final int bar;

    public MyObject(String foo, int bar) {
        this.foo = foo;
        this.bar = bar;
    }

    public String getFoo() {
        return foo;
    }

    public int getBar() {
        return bar;
    }
}

但是我倾向于这样写它们:

public class MyObject {
    public final String foo;
    public final int bar;

    public MyObject(String foo, int bar) {
        this.foo = foo;
        this.bar = bar;
    }
}

请注意,引用是最终的,因此对象仍然是不可变的。它使我可以编写更少的代码,并允许较短的访问(减少5个字符:getand ())。

我能看到的唯一缺点是,如果您想改变将来的实现getFoo()方式来做一些疯狂的事情,那您就做不到。但实际上,这永远不会发生,因为对象是不可变的。您可以在实例化过程中进行验证,在实例化过程中创建不可变的防御副本(ImmutableList例如,参见Guava的实例),并准备foobar对象以进行get呼叫。

我有什么劣势吗?

编辑

我想我还缺少的另一个缺点是序列化库使用了以get或开头的方法的反射is,但这是一个非常糟糕的做法...


我勒个去?final不会使变量成为对象不变。我通常使用一种设计,final在创建回调之前定义字段,以便回调可以访问这些字段。当然,它可以调用所有方法,包括任何setX方法。
托马什Zato

@TomášZato有上没有setX的方法StringintMyObject。例如,在第一个版本中,final仅确保类中构造函数以外的其他方法不要尝试bar = 7;。在第二个版本中,final有必要防止消费者这样做:MyObject x = new MyObject("hi", 5); x.bar = 7;
科里·肯德尔

1
好吧,“ 请注意引用是最终的,因此Object仍然是不可变的。 ”会误导您-这样看来您认为任何内容final Object都是不可变的,而事实并非如此。很抱歉对于这个误会。
托马什Zato

3
如果基础值是可变的,则private + accessors道路也不会阻止它- myObj.getFoo().setFrob(...)
贝尼·切尔尼亚夫斯基-帕斯金,2015年

Answers:


38

我能想到的四个缺点:

  1. 如果您希望同一实体具有只读且可变的形式,则常见的模式是拥有一个不可变的Entity类,该类仅公开具有受保护成员变量的访问器,然后创建一个MutableEntity来对其进行扩展并添加setter方法。您的版本阻止了它。
  2. 使用getter和setter遵循JavaBeans约定。如果要在基于属性的技术(如JSTL或EL)中将类用作Bean,则需要公开公共获取器。
  3. 如果您想更改实现以派生值或在数据库中查找值,则必须重构客户端代码。访问器/更改器方法仅允许您更改实现。
  4. 最不惊奇的是-当我看到公共实例变量时,我立即寻找可能是谁对其进行了突变,并担心由于封装丢失而打开潘多拉盒子。 http://en.wikipedia.org/wiki/基本原理

也就是说,您的版本绝对更加简洁。如果这是仅在特定程序包中使用的专门类(也许程序包范围在这里是个好主意),那么我可以一次性考虑一下。但是我不会公开这样的主要API。


3
好的答案;我不同意其中的一些,但是我不确定此站点是最佳讨论论坛。简而言之,1)我可以使用可变形式破坏其他人,因为他们认为它是不可变的(也许我应该在示例中将final添加到类中),2)我同意,3)我认为它不再是POJO。案例4)我现在同意,但是希望这样的讨论可以改变最令人惊讶的:)。
科里·肯德尔

2
5)您不能安全地公开固有可变的类/类型-如数组。安全的方法是每次从getter返回一个副本。
Clockwork-Muse

@ Clockwork-Muse,可以的,如果您认为人们不是白痴。当访问someObj.thisList.add()时,很明显您正在修改其他对象的状态。与someObj.getThisList()。add()未知。您需要询问文档或查找源代码,但是仅从方法声明中就无法分辨。
kritzikratzi 2014年

2
@kritzikratzi-问题是您不能保证您的代码不会遇到白痴。在某个时候它(甚至可能最终成为一个人!),而意外可能是一个巨大的问题。
Clockwork-Muse

1
好吧,让任何可变类型都继承自不可变类型本质上是错误的。请记住,不可变的类型可以保证“我不会改变。永远。”。
Deduplicator 2015年

26

也摆脱吸气剂/吸气剂,你就很好了!

在Java程序员中,这是一个备受争议的话题。

无论如何,在两种情况下,我使用公共变量代替(!)的getter / setter方法:

  1. 公共决赛对我来说,这标志着“我是一成不变的”,而不仅仅是一个吸气剂。在自动补全过程中,大多数IDE会用'F'指示最终修饰符。与getters / setter方法不同,在其中必须搜索是否缺少setXXX。
  2. 公共非决赛我喜欢这个数据类。我只是公开公开所有领域。没有getter,setter,构造函数。没什么。不到pojo。对我来说,这立即表示“看,我很傻。我拥有数据,仅此而已。将正确的数据放入我体内是您的工作”。Gson / JAXB / etc。处理这些类就好了。他们写得很幸福。毫无疑问,他们的目的或能力。最重要的是:您知道在更改变量时没有副作用。恕我直言,这导致了非常简洁的数据模型,几乎没有歧义,而getter和setter则存在一个巨大的问题,有时魔术会在其中发生。

5
很高兴偶尔看到一些理智:)
纳文

2
直到模型发展的一天到来,现在可以从另一个领域中提取一个旧领域。由于您想保持向后兼容性,因此必须保留旧字段,而不仅仅是更改getter和setter代码,您必须在使用此旧字段的其他所有地方进行更改,以确保它遵循新的模型表示形式。易于编写..是的...从长远来看,噩梦要维持...这就是为什么我们在C#属性访问器中仍然使用getter / setter或更好的方法。
Newtopian

9

用外行的话来说:

  • 违反封装是为了节省几行代码。这违反了OOD的目的。
  • 客户代码将与您的班级成员的姓名紧密结合在一起。耦合不好。OOD的整个目的是防止耦合。
  • 您还非常确定您的课程永远不需要可变。事情会改变的。变化是唯一不变的事情。

6
这如何违反封装?这两个版本彼此之间都暴露在外部世界中(具有只读访问权限)。但是,您对变更的硬耦合和僵化是正确的,我不会反对。
KChaloux

4
@KChaloux您将“封装违规”与“代码无法编译且不得不浪费时间修复”混淆了,首先发生的是。第二次发生在稍后。明确地说:目前已经违反了封装。当前您的类的内幕不再被封装。但是,如果将来类发生更改,则客户端代码将在将来中断,因为封装是在过去中断的。由于缺乏封装,今天有两个不同的“中断”,即今天的封装和明天的客户端代码。
图兰斯·科尔多瓦

8
从技术上讲,当您使用getters / etc时。客户端代码将与方法名称硬耦合。看看有多少个库必须包含带有“已弃用”标签/注释的旧版本的方法,因为较旧的代码仍然依赖于它们(并且某些人可能仍在使用它们,因为它们发现它们更易于使用,但您并非如此)应该这样做)。
JAB

5
实用的:程序员实际上多久重构一次私有字段名称,而不重命名公共获取者/设置者?据我所知,存在一个社区范围内的约定来命名它们,即使该约定可能只是IDE工具从字段名生成getter和setter的结果。理论上:但是,从面向对象建模的角度来看,任何公共项目都是公共合同的一部分,而不是内部实现细节。因此,如果有人决定公开最终领域,是不是他们说是他们承诺履行的合同?
Thomas Jung 2013年

3
@ user949300告诉我哪些可以学习。
图兰斯·科尔多瓦

6

我能看到的一个可能的缺点是,您与类中数据的内部表示联系在一起。这可能没什么大不了的,但是如果您使用设置器,并且决定从其他地方定义的其他类中返回foo和bar,则不需要更改使用MyObject的类。您只需要触摸MyObject。但是,如果使用裸值,则必须触摸我使用的MyObject的所有位置。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.