使用getter和setter(只获取和设置)而不是简单地为这些变量使用公共字段有什么好处?

如果getter和setter所做的不仅仅是简单的get/set,我可以很快地解决这个问题,但我不是100%清楚如何做到:

public String foo;

比:

private String foo;
public void setFoo(String foo) { this.foo = foo; }
public String getFoo() { return foo; }

而前者需要的样板代码要少得多。


当前回答

虽然getter和setter不常用,但这些方法的使用也可以用于AOP/代理模式的使用。例如,对于审计变量,您可以使用AOP来审计任何值的更新。没有getter/setter,除了到处更改代码外,其他都是不可能的。我个人从未使用过AOP,但它显示了使用getter/setter的另一个优势。

其他回答

公共字段并不比getter/setter对差,它除了返回字段并赋值之外什么都不做。任何差异都必须存在于其他因素,如可维护性或可读性。

getter/setter对的一个经常提到的优点不是。有一种说法是,您可以更改实现,而不必重新编译客户端。据推测,setter允许您稍后添加验证之类的功能,而您的客户甚至不需要知道它。然而,将验证添加到setter是对其前提条件的更改,违反了以前的合同,这很简单,“您可以将任何东西放在这里,稍后您可以从getter那里获得相同的东西”。

因此,现在您违反了合同,更改代码库中的每个文件是您应该做的事情,而不是避免。如果你避免这样做,你就假设所有的代码都假设这些方法的契约是不同的。

如果这不应该是约定,那么接口允许客户端将对象置于无效状态。这与封装正好相反。如果该字段从一开始就不能真正设置为任何值,为什么验证从一开始不存在?

同样的论点也适用于这些传递getter/setter对的其他假定优点:如果您稍后决定更改设置的值,那么您就违反了合同。如果您在派生类中重写默认功能,而不是进行一些无害的修改(如日志记录或其他不可观察的行为),那么您就违反了基类的约定。这违反了Liskov替代原则,这被视为OO的原则之一。

如果一个类对每个字段都有这些愚蠢的getter和setter,那么它就是一个没有不变量、没有契约的类。这真的是面向对象的设计吗?如果类只有那些getter和setter,那么它只是一个哑数据持有者,哑数据持有者应该看起来像哑数据持有者:

class Foo {
public:
    int DaysLeft;
    int ContestantNumber;
};

向此类类添加传递getter/setter对不会增加值。其他类应该提供有意义的操作,而不仅仅是字段已经提供的操作。这就是如何定义和维护有用的不变量。

客户:“我可以用这个类的对象做什么?”设计器:“你可以读写几个变量。”客户:“哦……我想很酷吧?”

使用getter和setter是有原因的,但如果这些原因不存在,那么以虚假封装之神的名义制作getter/setter对并不是一件好事。使用getter或setter的有效原因包括经常提到的稍后可以进行的潜在更改,如验证或不同的内部表示。或者,该值应该是客户端可读但不可写的(例如,读取字典的大小),因此简单的getter是一个不错的选择。但是,当你做出选择时,这些理由应该存在,而不仅仅是你以后可能想要的潜在原因。这是YAGNI(你不需要它)的一个例子。

访问器和赋值器的一个优点是可以执行验证。

例如,如果foo是公共的,我可以很容易地将其设置为null,然后其他人可以尝试调用对象上的方法。但它已经不在了!使用setFoo方法,我可以确保foo从未设置为null。

访问器和赋值器也允许封装-如果你不应该在值设置后看到它(也许它在构造函数中设置,然后被方法使用,但不应该被更改),那么任何人都不会看到它。但如果您允许其他类查看或更改它,则可以提供适当的访问器和/或赋值器。

DataStructure和Object之间存在差异。

数据结构应该暴露其内部而不是行为。

一个物体不应该暴露其内部,但它应该暴露其行为,这也被称为德米特定律

大多数DTO被认为是一种数据结构,而不是对象。他们应该只公开自己的数据,而不是行为。在数据结构中设置Setter/Getter将暴露行为,而不是其中的数据。这进一步增加了违反德梅特定律的可能性。

鲍勃叔叔在他的《干净的代码》一书中解释了得墨忒耳定律。

有一种著名的启发式方法叫做得墨忒耳定律,它说:模块不应该知道它的对象的内部结构操纵。正如我们在上一节中看到的,对象隐藏其数据并暴露操作。这意味着对象不应公开其通过访问器的内部结构,因为这样做是为了暴露,而不是隐藏其内部结构。更准确地说,德米特定律说C类的方法f应仅调用以下方法:Cf创建的对象作为参数传递给f的对象保存在C的实例变量中的对象该方法不应在任何允许的函数返回的对象上调用方法。换句话说,与朋友交谈,而不是与陌生人交谈。

因此,根据这一点,LoD违规的例子是:

final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();

在这里,函数应该调用它的直接朋友的方法,这里是ctxt,它不应该调用它直接朋友的朋友的方法。但该规则不适用于数据结构。所以在这里,如果ctxt、option、scratchDir是数据结构,那么为什么要用一些行为包装它们的内部数据,并违反LoD。

相反,我们可以这样做。

final String outputDir = ctxt.options.scratchDir.absolutePath;

这满足了我们的需求,甚至没有违反LoD。

灵感来源于Robert C.Martin(Bob叔叔)的“清洁代码”

在不支持“财产”(C++、Java)或在将字段更改为财产(C#)时需要重新编译客户端的语言中,使用get/set方法更容易修改。例如,向setFoo方法添加验证逻辑不需要更改类的公共接口。

在支持“真实”财产的语言中(Python、Ruby或Smalltalk?),没有必要使用get/set方法。

到目前为止,我在回答中遗漏了一个方面,即访问规范:

对于成员,设置和获取只有一个访问规范对于setter和getter,您可以对其进行微调并单独定义