首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >TW洞见〡getter和setter的那些事

TW洞见〡getter和setter的那些事

作者头像
ThoughtWorks
发布2018-04-16 10:41:00
8430
发布2018-04-16 10:41:00
举报
文章被收录于专栏:ThoughtWorksThoughtWorks

文章作者来自ThoughtWorks:佟达

相信每一个以Java或者C++作为编程入门语言的程序员,一定会记得一条金科玉律:字段(Filed)要声明成private,如果要读取或修改字段,就声明一些公开方法(Public Method),以get和set开头,像这段Java代码一样:

这些以get和set开头的方法,被称为getter和setter。时间久了,这种做法似乎成了一种神圣的约定,每个人都记得应该这么写,而忘记了为什么这么写。尤其是,当IDE变得足够智能,getter和setter可以自动生成,想要挑战这个约定的人就更少了——不过多按两下快捷键而已。

但是,当你写了很多程序,写过很多getter和setter,尤其是有些类方法,只有getter和setter时,总会有一天,你会疑惑,我到底为什么要这么干?

Why private field?

要解释为什么需要getter和setter,先要知道为什么字段应该是private的。

在汇编语言时,数据都是公开的。所谓公开,是指几乎任何指令,都可以作用在任意的数据块上。编写某段代码的程序员,通常知道自己在做什么,自己正在操作的数据代表什么含义,也就能选择合适的指令。而这段代码的用户——例如另一个程序员——可能并不知道数据的确切含义,比如把一个本应代表字符的数据块,当成数字进行计算,导致得到的结果和预期不符。

后来,类型的概念出现,某些操作开始只能作用在某些特定的类型上。以C语言为例,“*”这样的操作只能作用在数值类型上;而strcat函数则只能作用在char*类型上。这时,数据,和作用在数据上的函数,是分开的两部分,尽管两者之前保持着千丝万缕的关系。而一个函数,能够作用在哪些数据上,仅仅通过类型来限制,很难满足真实业务需求。比如,一个代表年龄的数值型变量,可能会被错误的传递给处理温度(也是数值型)的函数,得到一个负值作为返回值。

既然数据和函数是相关联的,何不将两者放在一起呢?于是在基本类型之上,更进一步的抽象被提出来,即数据,应该和相关的操作封装在一起。这就是对象(Object)的概念。一个对象,应该由该对象代表的数据,以及可以作用于这些数据的操作组合而成。

理想情况下,数据应该和所有相关的操作封装在一起,也就是说,除了这些操作外,不能有其他操作作用于这些数据。因此,数据需要被保护起来。这就是为什么Java, C++, C#等面向对象语言提供了private, protected, public等accessor来控制对数据和方法的访问权限。

另一方面,当前的编程语言,本质上都是图灵机的一种实现。每一个独立的代码单元,都可以看成一个作用在无限长纸带上的机器,这个机器存储着自己的内部状态,每次操作可以从纸带上的一个格子读取数据,然后计算一个结果输出到纸带上,同时更新自己的状态。这个机器的内部状态转移,对于计算结果的正确性,有着至关重要的作用。因此,要保证机器处于合法的状态,就必须保护内部状态,只在某些可控的操作下更新。

Why getter & setter?

数据需要被保护起来,而getter和setter是将数据暴露出来。看起来这是一对矛盾。

前面提到,每一个独立的代码单元都可以看成是一个图灵机。而要完成一个复杂任务,需要多个代码单元相互合作,组成更强大的图灵机。多个代码单元之间要合作,就不可避免的需要知道互相的状态,甚至一个代码单元需要修改另一个代码单元的状态。

也就是说,为了合作的需要,代码单元需要将数据暴露出来。

那么直接将数据字段设置为public,与通过getter和setter方式来暴露字段,有什么区别?

面向对象编程中有一条非常重要的原则,就是面向接口(Interface)编程。只要在一个稍具规模的团队工作过,就一定经历过与不同人写的代码进行集成的痛苦。不论设计阶段做的多么详尽,在开发过程中,接口都不可避免的会发生变化。一旦接口变化,所有与它相关的代码都要修改。所以,面向对象编程提出,尽量保证接口稳定,而内部逻辑可以改变,以达到最小化变化的目的。

如果直接将内部数据字段暴露出来,比如上面这段代码中的name,如果某天有一个新的需求,要求所有名字都用大写字母表示,就只能添加一个新的接口upperName,而使用name的地方,需要修改调用方式。如果采用文章开始时的代码,即添加getter和setter,有新需求出现时,只需修改getName方法,不需要修改调用处的代码,即可实现。

正是考虑到未来可能出现的功能扩展,所以像Java和C++这样的语言,即使还不确定是否应该将字段保护起来,也要写getter和setter,而这也导致了很多多余代码。

Why getter & setter, again?

然而,却并不是所有语言都是这样的。比如和Java最像的C#,虽然也建议将字段设置为private,但是却可以不用getter和setter。

上面这种property的写法,让Person的调用代码可以很直接的访问私有变量。

另一个提供property特性的语言是Python。

因为在C#和Python中,property的访问方式和直接将数据字段暴露出来的访问方式完全一样,所以在写代码时可以考虑先将数据暴露出来,避免过多的getter和setter,减少冗余代码。

One more thing…

Java代码的冗余是出了名的,同样的功能,像Python,甚至C#,可以写出更简洁,可读性更好的代码。不过,想要实现类似property的功能,也不是不可能。lombok提供了很多方便的注解来帮助Java程序员减少冗余代码。比如下面这段代码:

使用lombok,等价于下面这段代码:

看起来还不错。不过,因为这只是通过注解做的一种Hack,加了@Data注解,相当于编译器自动生成getter和setter,所以调用代码还是要用getId和getName这样的方法名来访问变量。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2015-08-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 思特沃克 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档