golang2021面向对象(31)封装
一个对象的变量或者方法如果对调用方是不可见的话,一般就被定义为"封装"。封装有时候也被叫做信息隐藏,同时也是面向对象编程最关键的一个方面。
Go语言只有一种控制可见性的手段:大写首字母的标识符会从定义它们的包中被导出,小写字母的则不会。这种限制包内成员的方式同样适用于struct或者一个类型的方法。因而如果我们想要封装一个对象,我们必须将其定义为一个struct。
这也就是前面的小节中IntSet被定义为struct类型的原因,尽管它只有一个字段:
type IntSet struct {
words []uint64
}
当然,我们也可以把IntSet定义为一个slice类型,但这样我们就需要把代码中所有方法里用到的s.words用*s替换掉了:
type IntSet []uint64
尽管这个版本的IntSet在本质上是一样的,但它也允许其它包中可以直接读取并编辑这个slice。换句话说,相对于*s这个表达式会出现在所有的包中,s.words只需要在定义IntSet的包中出现(译注:所以还是推荐后者吧的意思)。
这种基于名字的手段使得在语言中最小的封装单元是package,而不是像其它语言一样的类型。一个struct类型的字段对同一个包的所有代码都有可见性,无论你的代码是写在一个函数还是一个方法里。
封装提供了三方面的优点。首先,因为调用方不能直接修改对象的变量值,其只需要关注少量的语句并且只要弄懂少量变量的可能的值即可。
第二,隐藏实现的细节,可以防止调用方依赖那些可能变化的具体实现,这样使设计包的程序员在不破坏对外的api情况下能得到更大的自由。
把bytes.Buffer这个类型作为例子来考虑。这个类型在做短字符串叠加的时候很常用,所以在设计的时候可以做一些预先的优化,比如提前预留一部分空间,来避免反复的内存分配。又因为Buffer是一个struct类型,这些额外的空间可以用附加的字节数组来保存,且放在一个小写字母开头的字段中。这样在外部的调用方只能看到性能的提升,但并不会得到这个附加变量。Buffer和其增长算法我们列在这里,为了简洁性稍微做了一些精简:
type Buffer struct {
buf []byte
initial [64]byte
/* ... */
}
// Grow expands the buffer"s capacity, if necessary,
// to guarantee space for another n bytes. [...]
func (b *Buffer) Grow(n int) {
if b.buf == nil {
b.buf = b.initial[:0] // use preallocated space initially
}
if len(b.buf)+n > cap(b.buf) {
buf := make([]byte, b.Len(), 2*cap(b.buf) + n)
copy(buf, b.buf)
b.buf = buf
}
}
封装的第三个优点也是最重要的优点,是阻止了外部调用方对对象内部的值任意地进行修改。因为对象内部变量只可以被同一个包内的函数修改,所以包的作者可以让这些函数确保对象内部的一些值的不变性。比如下面的Counter类型允许调用方来增加counter变量的值,并且允许将这个值reset为0,但是不允许随便设置这个值(译注:因为压根就访问不到):
type Counter struct { n int }
func (c Counter) N() int { return c.n }
func (c *Counter) Increment() { c.n++ }
func (c *Counter) Reset() { c.n = 0 }
只用来访问或修改内部变量的函数被称为setter或者getter,例子如下,比如log包里的Logger类型对应的一些函数。在命名一个getter方法时,我们通常会省略掉前面的Get前缀。这种简洁上的偏好也可以推广到各种类型的前缀比如Fetch,Find或者Lookup。
package log
type Logger struct {
flags int
prefix string
// ...
}
func (l *Logger) Flags() int
func (l *Logger) SetFlags(flag int)
func (l *Logger) Prefix() string
func (l *Logger) SetPrefix(prefix string)
Go的编码风格不禁止直接导出字段。当然,一旦进行了导出,就没有办法在保证API兼容的情况下去除对其的导出,所以在一开始的选择一定要经过深思熟虑并且要考虑到包内部的一些不变量的保证,未来可能的变化,以及调用方的代码质量是否会因为包的一点修改而变差。
封装并不总是理想的。 虽然封装在有些情况是必要的,但有时候我们也需要暴露一些内部内容,比如:time.Duration将其表现暴露为一个int64数字的纳秒,使得我们可以用一般的数值操作来对时间进行对比,甚至可以定义这种类型的常量:
const day = 24 * time.Hour
fmt.Println(day.Seconds()) // "86400"
另一个例子,将IntSet和本章开头的geometry.Path进行对比。Path被定义为一个slice类型,这允许其调用slice的字面方法来对其内部的points用range进行迭代遍历;在这一点上,IntSet是没有办法让你这么做的。
这两种类型决定性的不同:geometry.Path的本质是一个坐标点的序列,不多也不少,我们可以预见到之后也并不会给他增加额外的字段,所以在geometry包中将Path暴露为一个slice。相比之下,IntSet仅仅是在这里用了一个[]uint64的slice。这个类型还可以用[]uint类型来表示,或者我们甚至可以用其它完全不同的占用更小内存空间的东西来表示这个集合,所以我们可能还会需要额外的字段来在这个类型中记录元素的个数。也正是因为这些原因,我们让IntSet对调用方不透明。
在这章中,我们学到了如何将方法与命名类型进行组合,并且知道了如何调用这些方法。尽管方法对于OOP编程来说至关重要,但他们只是OOP编程里的半边天。
美股开盘科技股拖累纳指跌近百点区块链概念重挫Coinbase跌逾27金融界5月11日消息,投资者权衡超预期CPI数据并等待美联储官员讲话,同时美债收益率回落提振市场情绪,美股开盘涨跌互现,道指小幅开涨约30点,纳指跌0。8。区块链概念股大型科技股普
燃油车时代的终结截止2021年的数据,欧洲主要国家的新能源渗透率挪威以超过60遥遥领先,其次是冰岛在30左右,第三名为芬兰在20左右。除了个别国家外(芬兰法国葡萄牙爱尔兰和比利时),欧洲主要国家的
AMD前高管加盟英特尔,担任企业发展部门高级副总裁5月11日晚间,英特尔通过官微宣布,任命MattPoirier担任企业发展部门高级副总裁,自2022年5月30日起生效。英特尔表示,Poirier将领导英特尔的企业发展团队,全权负
搜狗地图官宣5月15日下线,将关闭所有服务,此前App已下架Tech星球5月11日消息,据IT之家,搜狗地图官网显示,搜狗地图将于2022年5月15日23点正式下线,届时关闭所有相关服务,并建议用户下载腾讯地图。公开资料显示,搜狗地图原名图
做点实事吧裁员经济下行互联网寒冬充斥着2022年,让每个人心中蒙上一层阴影,每个人都很难。不过,今天我想要鼓励一下你,疫情总会过去,寒冬也总会过去,现在我们应该做的就是,积蓄能量,厚积薄发。
支付宝的转变刚刚睡醒,就突然意识到支付宝又发生了改变,这个改变提醒我们要时刻提高警惕了!就在昨天晚上,支付宝突然之间就变得很简洁,消费者可以自己把首页的活动推荐取消掉,还可以把一些生活频道都关
北京学而思被列入经营异常,好未来正进行法人地址和经营范围变更记者陈振芳近日,据国家企业信用信息公示系统显示,北京学而思网络科技有限公司(下称北京学而思)因登记住所或者经营场所无法联系,被北京市海淀区市场监督管理局列入经营异常名录。天眼查工作
vivo手机广告关闭流程1点击设置,找到安全,找到更多安全设置然后找到广告与隐私,把个性化广告推荐关掉,然后返回上个界面,找到应用安装把应用推荐关掉,然后在设置主页,找到系统管理,在找到全局搜索,然后打开
购买相机储存卡,你需要了解这些一储存卡类型市场上常见的储存卡有三种,分别是SD卡TF卡(MicroSD卡)CFexpress卡SD卡SD卡是最常见的,大部分相机卡片机用的都是SD卡。SD卡最大的特点是有写保护开
大疆重磅新品上线!最适合小白的无人机,超轻巨能飞5月10日晚,大疆创新发布了消费级无人机新成员DJIMini3Pro,极果君体验过后大为震撼。小小的身材,却能实现如此多的功能,这可能是全系里最适合普通人入手的真入门级全能无人机了
久等了!超精致微型书架箱EmbersLab黑烬EL3超详细试用报告在EmbersLab黑烬EL3的开箱视频之后,很多人就在催更这一全新国产品牌第一款产品的评测!但由于这款产品本身在摩大大的工作中就属于插队产品(因为到现在为止,春节前送过来的一些产