微信号:programmer_club

介绍:程序员第一自媒体,与你探讨码农人生路上遇到的各类泛技术话题,定期为你推荐码农人生思考、感悟以及启迪!

TW洞见〡getter和setter的那些事

2015-08-17 11:25 佟达
点击上方 “思特沃克”可以订阅哦!

文章作者来自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这样的方法名来访问变量。

点击下方 “阅读原文”查看更多精彩文章。
↓↓↓
欢迎关注
新版技术雷达
各类干货洞见
精选职位招聘
活动预告总结
长按二维码可以快速关注我们~



 
程序员之家 更多文章 我们这一代人的困惑 神剪辑,揭秘程序员加班内幕,不能看,看完想笑又想哭! 美国12位创新型程序员:让科技永远改变 说说怎么写clean code 500,000+年薪程序猿出身哪里 猎聘网揭秘前十大学校
猜您喜欢 入门软件工程师所面临的5个挑战 就是这款神奇软件,终结了苹果无病毒神话 开源一个社交网站模拟登录的库 响应式布局 Java程序员面试失败的5大原因