为什么使用getter和setter/访问器?

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

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

520079 次浏览

因为从现在起2周(月,年),当你意识到你的setter需要做更多而不仅仅是设置值时,你也会意识到该属性已直接用于238个其他类:-)

访问器和修改器的一个优点是您可以执行验证。

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

访问器和修改器也允许封装——如果你不应该在值设置后看到它(也许它在构造函数中设置,然后被方法使用,但永远不应该被改变),它永远不会被任何人看到。但是如果你可以允许其他类看到或改变它,你可以提供适当的访问器和/或修改器。

OO设计的基本原则之一:封装!

它为您提供了许多好处,其中之一是您可以在幕后更改getter/setter的实现,但只要数据类型保持不变,该值的任何消费者都将继续工作。

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

在支持“真实”属性的语言(Python,Ruby,也许是Smalltalk?)中,没有获取/设置方法的意义。

此外,这是为了“面向未来”你的类。特别是,从字段更改为属性是ABI中断,所以如果你后来决定你需要更多的逻辑而不仅仅是“设置/获取字段”,那么你需要破坏ABI,这当然会给已经针对你的类编译的任何其他东西带来问题。

有很多原因。我最喜欢的一个是当你需要改变行为或调节你可以在变量上设置的内容时。例如,假设你有一个setSpeed(int速度)方法。但是你希望你只能设置最大速度为100。你会做这样的事情:

public void setSpeed(int speed) {if ( speed > 100 ) {this.speed = 100;} else {this.speed = speed;}}

现在,如果您在代码中的任何地方都在使用公共字段,然后您意识到您需要上述要求,该怎么办?

我的2美分:)

另一个用途(在支持属性的语言中)是setter和getter可以暗示操作是非平凡的。通常,您希望避免在属性中执行任何计算上昂贵的操作。

取决于你的语言。你已经标记了“面向对象”而不是“Java”,所以我想指出ChssPly76的答案是依赖于语言的。例如,在Python中,没有理由使用getter和setter。如果你需要更改行为,你可以使用一个属性,它将getter和setter包装在基本属性访问周围。类似这样的东西:

 class Simple(object):def _get_value(self):return self._value -1
def _set_value(self, new_value):self._value = new_value + 1
def _del_value(self):self.old_values.append(self._value)del self._value
value = property(_get_value, _set_value, _del_value)

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

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

实际上有很多很好的理由来考虑使用访问器而不是直接暴露类的字段-不仅仅是封装的参数,并使未来的更改更容易。

以下是我知道的一些原因:

  • 封装与获取或设置属性相关的行为-这允许稍后更轻松地添加附加功能(如验证)。
  • 隐藏属性的内部表示,同时使用替代表示公开属性。
  • 将公共接口与更改隔离-允许公共接口在实现更改时保持不变,而不会影响现有消费者。
  • 控制属性的生命周期和内存管理(处置)语义学-在非托管内存环境(如C++或Objective-C)中尤为重要。
  • 为属性在运行时更改提供调试拦截点-在某些语言中,如果没有此功能,调试属性更改为特定值的时间和位置可能非常困难。
  • 改进了与旨在针对属性getter/setter进行操作的库的互操作性-想到了模拟、序列化和WPF。
  • 允许继承者通过重写getter/setter方法来更改属性行为和公开方式的语义学。
  • 允许getter/setter作为lambda表达式而不是值传递。
  • Getter和setter可以允许不同的访问级别-例如,get可以是公共的,但集合可以是受保护的。

它对延迟加载很有用。假设有问题的对象存储在数据库中,除非你需要它,否则你不想去获取它。如果对象被getter检索,那么内部对象可以为空,直到有人要求它,然后你可以在第一次调用getter时去获取它。

我在一个项目中有一个基础页面类,它从几个不同的Web服务调用中加载一些数据,但是这些Web服务调用中的数据并不总是在所有子页面中使用。Web服务,尽管有所有的好处,开创了“慢”的新定义,所以如果你没有必要的话,你不想进行Web服务调用。

我从公共字段转移到getter,现在getter检查缓存,如果没有,则调用Web服务。因此,通过一点包装,阻止了很多Web服务调用。

因此,getter使我不必在每个子页面上试图找出我需要什么。如果我需要它,我调用getter,如果我还没有它,它会为我找到它。

    protected YourType _yourName = null;public YourType YourName{get{if (_yourName == null){_yourName = new YourType();return _yourName;}}}

在面向对象语言中,方法及其访问修饰符声明了该对象的接口。在构造函数与访问器和修改器方法之间,开发人员可以控制对对象内部状态的访问。如果变量被简单地声明为公共的,那么就没有办法调节这种访问。当我们使用setter时,我们可以限制用户输入我们需要的内容。这意味着该变量的提要将通过适当的通道来,并且通道是我们预定义的。因此使用setter更安全。

很多人都在谈论getter和setter的优点,但我想扮演魔鬼的拥护者。现在我正在调试一个非常大的程序,程序员决定将所有内容都设置为getter和setter。这可能看起来不错,但这是一个逆向工程的噩梦。

假设您正在查看数百行代码,并且遇到了以下内容:

person.name = "Joe";

这是一段非常简单的代码,直到你意识到它是一个setter。现在,你跟踪那个setter,发现它还设置person.firstName,person.lastName,person.isHuman,person.hasReallyCommon FirstName,并调用person.update(),它将查询发送到数据库,等等。

乍一看就能理解一段本地代码是getter和setter容易破坏的具有良好易读性的重要属性。这就是为什么我尽可能避免它们,并在使用它们时尽量减少它们所做的事情。

我只想抛出注释的想法:@getter和@setter。使用@getter,您应该能够obj=class.field但不能class.field=obj。使用@setter,反之亦然。使用@getter和@setter,您应该能够同时做到这一点。这将保留封装并通过在运行时不调用琐碎的方法来减少时间。

我花了相当长的一段时间来思考Java的情况,我相信真正的原因是:

  1. 代码到接口,而不是实现
  2. 接口只指定方法,不指定字段

换句话说,在接口中指定字段的唯一方法是提供写入新值的方法和读取当前值的方法。

这些方法是臭名昭著的getter和setter。

我想发布一个我刚刚完成的真实世界的例子:

背景-我休眠工具来为我的数据库生成映射,我在开发时正在更改数据库。我更改数据库模式,推送更改,然后运行休眠工具来生成java代码。一切都很好,直到我想向这些映射实体添加方法。如果我修改生成的文件,每次我对数据库进行更改时,它们都会被覆盖。所以我像这样扩展生成的类:

package com.foo.entities.customclass User extends com.foo.entities.User{public Integer getSomething(){return super.getSomething();}public void setSomething(Integer something){something+=1;super.setSomething(something);}}

我上面所做的是用我的新功能(一些+1)覆盖超类上的现有方法,而不接触基类。如果你一年前写了一个类,想在不改变基类的情况下进入版本2(测试噩梦)。希望这有帮助。

谢谢,这真的澄清了我的想法。现在这里有(几乎)10个(几乎)不使用getter和setter的好理由:

  1. 当您意识到您需要做的不仅仅是设置和获取值时,您可以将该字段设为私有,这将立即告诉您直接访问它的位置。
  2. 您在其中执行的任何验证都只能是上下文无关的,而验证在实践中很少。
  3. 您可以更改正在设置的值-当调用者传递给您一个他们[震惊恐怖]希望您按原样存储的值时,这绝对是一场噩梦。
  4. 您可以隐藏内部表示-太棒了,所以您确保所有这些操作都是对称的,对吗?
  5. 您已经将您的公共接口与表下的更改隔离开来-如果您正在设计一个接口并且不确定是否可以直接访问某些东西,那么您应该继续设计。
  6. 一些库期望这样,但不是很多-反射,序列化,模拟对象都可以很好地处理公共字段。
  7. 继承这个类,您可以覆盖默认功能——换句话说,您不仅可以隐藏实现,还可以使其不一致,从而真正混淆调用者。

最后三个我刚离开(N/A或D/C)…

从面向对象设计的角度来看,这两种替代方案都会削弱类的封装,从而损害代码的维护。有关讨论,您可以查看这篇精彩的文章:http://typicalprogrammer.com/?p=23

除非当前交付需要,否则不要使用getter setter,即不要过多考虑将来会发生什么,如果要更改的任何内容在大多数生产应用程序,系统中都是更改请求。

认为简单,容易,在需要时添加复杂性。

我不会利用企业所有者的无知,因为我认为这是正确的,或者我喜欢这种方法。

我写了大量的系统,没有getters setters,只有访问修饰符和一些方法来验证n执行biz逻辑。如果你绝对需要。使用任何东西。

好吧,我只是想补充一点,即使有时它们对于变量/对象的封装和安全性是必要的,如果我们想编写一个真正的面向对象程序,那么我们需要停止过度使用ACCESSORS,因为有时我们在不必要的时候非常依赖它们,这几乎与我们将变量公之于众一样。

公共字段并不比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(你不会需要它的)的一个实例。

代码发展private非常适合您需要数据成员保护。最终,所有类都应该是具有明确定义的接口你不能随便摆弄它的内部的“小程序”。

也就是说,软件开发不是设定类的最终版本,就好像你在第一次尝试时压一些铸铁雕像一样。当你使用它时,代码更像粘土。它在进化当你开发它并了解更多关于你正在解决的问题领域时。在开发过程中,类可能会相互交互(你计划排除的依赖关系),合并或分裂。所以我认为这场辩论归结为人们不想虔诚地编写

int getVar() const { return var ; }

所以你有:

doSomething( obj->getVar() ) ;

而不是

doSomething( obj->var ) ;

getVar()不仅在视觉上是嘈杂的,它给人一种错觉,认为gettingVar()是一个比实际更复杂的过程。如果你的类有一个直通设置器,你(作为类编写者)如何看待var的神圣性对你的类的用户来说尤其令人困惑--然后看起来你是在设置这些大门来“保护”你坚持认为有价值的东西(var的神圣性),但即使你承认var的保护也没有多大价值,因为任何人都可以进来,setvar达到他们想要的任何价值,你甚至没有偷看他们在做什么。

所以我编写如下程序(假设使用“敏捷”类型的方法——即当我编写代码时不知道完全它将要做什么/没有时间或经验来规划一个精心设计的瀑布式界面集):

1)从具有数据和行为的基本对象的所有公共成员开始。这就是为什么在我所有的C++“示例”代码中,你会注意到我到处都使用struct而不是class

2)当一个对象的数据成员的内部行为变得足够复杂时,(例如,它喜欢以某种顺序保持内部std::list),就会编写访问器类型的函数。因为我是自己编程的,所以我并不总是马上设置成员private,但在类的演变过程中,该成员将被“提升”为protectedprivate

3)完全充实并对其内部有严格规则的类(即他们确切地知道它们在做什么,并且你不能用它的内部“干”(技术术语))被赋予class指定,默认私有成员,只有少数成员被允许成为public

我发现这种方法可以让我避免坐在那里虔诚地编写getter/setter,当很多数据成员在类进化的早期阶段被迁移、转移等时。

我们使用getter和setter:

  • 可重用性
  • 在编程的后期阶段执行验证

Getter和setter方法是访问私有类成员的公共接口。


封装咒语

封装咒语是使字段私有,方法公开。

Getter方法:我们可以访问私有变量。

Setter方法:我们可以修改私有字段。

即使getter和setter方法没有添加新功能,我们也可以改变主意稍后再回来制作该方法

  • 更好;
  • 更安全;和
  • 更快。

任何可以使用值的地方,都可以添加返回该值的方法。而不是:

int x = 1000 - 500

使用

int x = 1000 - class_name.getValue();

用外行的话说

Person

假设我们需要存储这个Person的详细信息。这个Person有字段nameagesex。这样做涉及为nameagesex创建方法。现在,如果我们需要创建另一个人,就有必要重新创建nameagesex的方法。

相反,我们可以使用getter和setter方法创建一个beanclass(Person)。所以明天我们可以在需要添加新人时创建这个Beanclass(Person class)的对象(见图)。因此,我们重用了bean类的字段和方法,这要好得多。

我能想到一个你不希望一切公开的原因。

例如,您从未打算在类之外使用的变量可以被访问,甚至可以通过链变量访问(即object.item.origin.x)进行非直接访问。

通过将大部分内容都设为私有,并且仅将您想要扩展的内容和可能在子类中引用的内容设为protected,并且通常仅将静态最终对象设为public,那么您可以控制其他程序员和程序可以在API中使用的内容以及它可以访问的内容以及它不能通过使用setter和getter来访问您想要的内容程序,或者实际上可能只是碰巧使用您的代码的其他程序员可以在您的程序中进行修改。

如果您想要一个只读变量,但不希望客户端必须更改访问它的方式,请尝试以下模板化类:

template<typename MemberOfWhichClass, typename primative>class ReadOnly {friend MemberOfWhichClass;public:template<typename number> inline bool   operator==(const number& y) const { return x == y; }template<typename number> inline number operator+ (const number& y) const { return x + y; }template<typename number> inline number operator- (const number& y) const { return x - y; }template<typename number> inline number operator* (const number& y) const { return x * y; }template<typename number> inline number operator/ (const number& y) const { return x / y; }template<typename number> inline number operator<<(const number& y) const { return x << y; }template<typename number> inline number operator^(const number& y) const  { return x^y; }template<typename number> inline number operator~() const                 { return ~x; }template<typename number> inline operator number() const                  { return x; }protected:template<typename number> inline number operator= (const number& y) { return x = y; }template<typename number> inline number operator+=(const number& y) { return x += y; }template<typename number> inline number operator-=(const number& y) { return x -= y; }template<typename number> inline number operator*=(const number& y) { return x *= y; }template<typename number> inline number operator/=(const number& y) { return x /= y; }primative x;};

示例使用:

class Foo {public:ReadOnly<Foo, int> cantChangeMe;};

请记住,您还需要添加按位和一元操作符!这只是为了让您开始

考虑使用访问器有一个很好的理由,因为没有属性继承。参见下一个例子:

public class TestPropertyOverride {public static class A {public int i = 0;
public void add() {i++;}
public int getI() {return i;}}
public static class B extends A {public int i = 2;
@Overridepublic void add() {i = i + 2;}
@Overridepublic int getI() {return i;}}
public static void main(String[] args) {A a = new B();System.out.println(a.i);a.add();System.out.println(a.i);System.out.println(a.getI());}}

输出:

004

您应该在以下情况下使用getter和setter:

  • 您正在处理的东西在概念上是一个属性,但是:
    • 您的语言没有属性(或类似的机制,如Tcl的变量跟踪),或
    • 您的语言的属性支持不足以满足此用例,或
    • 您的语言(或有时您的框架)的惯用约定鼓励此用例的getter或setter。

所以这很少是一个一般的OO问题;这是一个特定于语言的问题,对于不同的语言(和不同的用例)有不同的答案。


从面向对象理论的角度来看,getter和setter是没用的。你的类的接口决定它做什么,而不是它的状态是什么。(如果没有,你写错类了。)在非常简单的情况下,一个类所做的只是,例如,表示直角坐标中的一个点,*属性是接口的一部分;getter和setter只是掩盖了这一点。但在除了非常简单的情况之外,属性、getter和setter都不是接口的一部分。

换句话说:如果你认为你的类的使用者甚至不应该知道你有一个spam属性,更不用说随意改变它了,那么给他们一个set_spam方法是你最不想做的事情。

*即使对于那个简单的类,你也不一定想允许设置xy的值。如果这真的是一个类,它不应该有translaterotate等方法吗?如果它只是一个类,因为你的语言没有记录/结构/命名元组,那么这不是一个真正的面向对象的问题…


但是从来没有人做过通用的OO设计。他们用特定的语言进行设计和实现。在某些语言中,getter和setter远非无用。

如果您的语言没有属性,那么表示概念上是属性但实际上是计算或验证的东西的唯一方法就是通过getter和setter。

即使您的语言确实具有属性,也可能存在属性不足或不合适的情况。例如,如果您想允许子类控制属性的语义学,在没有动态访问的语言中,子类不能将计算属性替换为属性。

至于“如果我想稍后更改我的实现怎么办?”的问题(在OP的问题和接受的答案中以不同的措辞重复了多次):如果它真的是一个纯实现的更改,并且你从一个属性开始,你可以在不影响接口的情况下将其更改为属性。当然,除非你的语言不支持。所以这真的只是同样的情况。

此外,遵循你正在使用的语言(或框架)的习惯用法也很重要。如果你用C#编写漂亮的Ruby风格代码,那么除了你之外的任何有经验的C#开发人员都无法阅读它,这很糟糕。有些语言在其约定方面的文化比其他语言更强。-Java和Python碰巧拥有两种最强大的文化,这可能不是巧合,因为它们在惯用getter方面处于相反的两端。

除了人类读者之外,还会有一些库和工具希望你遵循惯例,如果你不这样做,会让你的生活变得更加艰难。将Interface Builder小部件挂钩到ObjC属性之外的任何东西,或者使用某些没有getter的Java嘲笑库,只会让你的生活更加困难。如果这些工具对你很重要,不要与它们抗争。

在纯面向对象的世界里,getter和setter是可怕的反模式。阅读这篇文章:盖特/盖特,邪恶,句号。简而言之,它们鼓励程序员将对象视为数据结构,这种思维是纯过程的(就像COBOL或C一样)。在面向对象语言中,没有数据结构,只有暴露行为的对象(不是属性/属性!)

您可以在优雅的物体的第3.5节(我关于面向对象编程的书)中找到更多关于它们的信息。

编辑:我回答这个问题是因为有很多学习编程的人会问这个问题,大多数答案在技术上都很有能力,但是如果你是新手,它们就不那么容易理解了。我们都是新手,所以我想我会尝试一个对新手更友好的答案。

两个主要的是多态性和验证。即使它只是一个愚蠢的数据结构。

假设我们有这个简单的类:

public class Bottle {public int amountOfWaterMl;public int capacityMl;}

一个非常简单的类,它包含有多少液体,以及它的容量是多少(以毫升为单位)。

当我这样做时会发生什么:

Bottle bot = new Bottle();bot.amountOfWaterMl = 1500;bot.capacityMl = 1000;

好吧,你不会期望这工作,对吧?你希望有某种健全性检查。更糟糕的是,如果我从未指定最大容量怎么办?哦,天哪,我们有一个问题。

但是还有另一个问题,如果瓶子只是一种容器呢?如果我们有几个容器,每个容器都有容量和液体的数量呢?如果我们可以做一个接口,我们可以让我们的程序接受这个接口,瓶子、易拉罐和各种各样的东西就可以互换工作。那不是更好吗?既然接口需要方法,这也是一件好事。

我们最终会得到这样的结果:

public interface LiquidContainer {public int getAmountMl();public void setAmountMl(int amountMl);public int getCapacityMl();}

太好了!现在我们只需将Bottle更改为:

public class Bottle extends LiquidContainer {private int capacityMl;private int amountFilledMl;
public Bottle(int capacityMl, int amountFilledMl) {this.capacityMl = capacityMl;this.amountFilledMl = amountFilledMl;checkNotOverFlow();}
public int getAmountMl() {return amountFilledMl;}
public void setAmountMl(int amountMl) {this.amountFilled = amountMl;checkNotOverFlow();}public int getCapacityMl() {return capacityMl;}
private void checkNotOverFlow() {if(amountOfWaterMl > capacityMl) {throw new BottleOverflowException();}}

我将BottleOverflow Exception的定义留给读者作为练习。

现在请注意这是多么健壮。我们现在可以通过接受LiquidContainer而不是Bottle来处理代码中的任何类型的容器。这些瓶子如何处理这种东西都是不同的。您可以让瓶子在更改时将其状态写入磁盘,或者保存在SQL数据库或GNU知道其他什么的瓶子。

所有这些都可以有不同的方法来处理各种Whoopsies。Bottle只是检查,如果它溢出,它会抛出一个RuntimeException。但这可能是错误的做法。(关于错误处理有一个有用的讨论,但我在这里故意保持非常简单。评论中的人可能会指出这种简单方法的缺陷. ;) )

是的,似乎我们从一个非常简单的想法快速获得更好的答案。

还请注意,您不能更改瓶子的容量。它现在是固定的。您可以通过声明它是最终的来使用int来执行此操作。但如果这是一个列表,您可以清空它,向其添加新内容,等等。您不能限制对触摸内部的访问。

还有第三件不是每个人都解决的事情:getter和setter使用方法调用。这意味着它们看起来像其他任何地方的普通方法。你到处都有同样的事情,而不是为DTO和其他东西提供奇怪的特定语法。

吸气剂setters用于实现面向对象编程的两个基本方面:

  1. 抽象
  2. 封装

假设我们有一个员工类:

package com.highmark.productConfig.types;
public class Employee {
private String firstName;private String middleName;private String lastName;
public String getFirstName() {return firstName;}public void setFirstName(String firstName) {this.firstName = firstName;}public String getMiddleName() {return middleName;}public void setMiddleName(String middleName) {this.middleName = middleName;}public String getLastName() {return lastName;}public void setLastName(String lastName) {this.lastName = lastName;}
public String getFullName(){return this.getFirstName() + this.getMiddleName() +  this.getLastName();}}

在这里,全名的实现细节对用户是隐藏的,用户不能直接访问,这与公共属性不同。

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

我知道有点晚了,但我认为有些人对表演感兴趣。

我做了一点性能测试。我写了一个类“NumberHolder”,它包含一个整数。您可以使用getter方法读取该整数anInstance.getNumber()或使用anInstance.number直接访问数字。我的程序通过两种方式读取数字1,000,000,000次。该过程重复五次并打印时间。我得到了以下结果:

Time 1: 953ms, Time 2: 741msTime 1: 655ms, Time 2: 743msTime 1: 656ms, Time 2: 634msTime 1: 637ms, Time 2: 629msTime 1: 633ms, Time 2: 625ms

(时间1是直接方式,时间2是getter)

你看,getter(几乎)总是快一点。然后我尝试了不同数量的循环。而不是100万,我用1000万和10万。结果:

1000万周期:

Time 1: 6382ms, Time 2: 6351msTime 1: 6363ms, Time 2: 6351msTime 1: 6350ms, Time 2: 6363msTime 1: 6353ms, Time 2: 6357msTime 1: 6348ms, Time 2: 6354ms

1000万周期,时间几乎相同。以下是10万(10万)循环:

Time 1: 77ms, Time 2: 73msTime 1: 94ms, Time 2: 65msTime 1: 67ms, Time 2: 63msTime 1: 65ms, Time 2: 65msTime 1: 66ms, Time 2: 63ms

此外,在不同的循环量下,吸气剂比常规方法快一点。我希望这对你有帮助。

getter/setter的一个相对现代的优点是可以更轻松地浏览标记(索引)代码编辑器中的代码。例如。如果您想查看谁设置了一个成员,您可以打开setter的调用层次结构。

另一方面,如果该成员是公共的,则工具无法过滤对该成员的读/写访问。因此,您必须艰难地处理该成员的所有用途。

来自数据隐藏的Getter和setter。数据隐藏意味着我们对外人隐藏数据或外部人员/事物无法访问我们的数据。这是OOP中一个有用的特性。

举个例子:

如果您创建了一个公共变量,您可以访问该变量并在任何地方(任何类)更改值。但是如果您创建为私有,则该变量无法在除声明类之外的任何类中看到/访问。

publicprivate是访问修饰符。

那么我们如何在外部访问该变量:

这是getterssetters的来源。您可以将变量声明为私有,然后您可以为该变量实现getter和setter。

示例(Java):

private String name;
public String getName(){return this.name;}
public void setName(String name){this.name= name;}

优势:

当任何人想要访问或更改/设置值为balance变量时,他/她必须具有权限。

//assume we have person1 object//to give permission to check balanceperson1.getName()
//to give permission to set balanceperson1.setName()

您也可以在构造函数中设置值,但当以后需要时要更新/更改值,您必须实现setter方法。

根据我的经验,将变量设置为私有并为每个变量提供访问器和修饰符是理想的。

通过这种方式,您可以创建只读变量,也可以根据您的要求只写变量。

下面的实现显示了一个只写变量。

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

下面显示了一个只读变量。

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

数据结构和对象之间存在差异。

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

对象不应该暴露其内部,但应该暴露其行为,这也称为LoD原则

大多数情况下,DTO被认为更像是数据结构而不是对象。它们应该只暴露它们的数据而不是行为。在数据结构中使用Setter/Getter将暴露行为而不是其中的数据。这进一步增加了违反LoD原则的机会。

Bob叔叔在他的书Clean code中解释了LoD原则。

有一个著名的启发式称为LoD原则,它说模块不应该知道它的对象的内部正如我们在上一节中看到的,对象隐藏了它们的数据并公开操作。这意味着对象不应公开其通过访问器的内部结构,因为这样做是为了暴露,而不是隐藏它的内部结构。

更准确地说,LoD原则说C类的方法f应该只调用这些方法:

  • C
  • 由f创建的对象
  • 作为参数传递给f的对象
  • 在C的实例变量中保存的对象

该方法不应调用任何允许函数返回的对象的方法。与朋友交谈,而不是陌生人。

所以根据这个,违反LoD的例子是:

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

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

相反,我们可以做这样的事情。

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

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

灵感来自Robert C. Martin(Bob叔叔)的Clean Code

如果你不需要任何验证,甚至不需要维护状态,即一个属性依赖于另一个属性,所以我们需要在一个属性发生变化时维护状态。你可以通过公开字段而不使用getter和setter来保持简单。

我认为随着程序的增长,OOP会使事情变得复杂,它成为开发人员扩展的噩梦。

一个简单的例子;我们从xml生成c++标头。标头包含不需要任何验证的简单字段。但是仍然像OOPS访问器一样,我们按照以下方式生成它们。

const Filed& getfield() constField& getField()void setfield(const Field& field){...}

这是非常冗长的,不是必需的

struct{Field field;};

是足够的和可读的。函数式编程没有数据隐藏的概念,它们甚至不需要它,因为它们不会改变数据。