Go语言必知必会100问题-10 小心类型嵌入导致的问题

小心类型嵌入导致的问题

在定义结构体时,Go语言支持通过类型嵌入的形式定义结构体字段。但是,如果我们没有真正理解类型嵌入的意义,有时可能会导致意想不到的行为。本文将主要分析如何嵌入类型,类型嵌入的作用以及可能出现的问题。

在Go语言中,如果定义的结构体字段没有名称,则称该字段为嵌入字段。例如下面结构体Foo中的Bar是嵌入的。因为Bar类型被声明没有关联名称,所以它是一个嵌入字段。

type Foo struct {
        Bar
}
type Bar struct {
        Baz int
}

嵌入可以用来提升嵌入类型的字段和方法,像上面的代码,由于Bar包含一个Baz字段,它被提升到Foo中,就好像Foo中定义了一个Baz字段一样。

在这里插入图片描述

因此,可以通过Foo直接访问Baz字段。

foo := Foo{}
foo.Baz = 42

注意,Baz可从两个不同的路径获得。既可以使用Foo.Baz,也可以通过Bar采用Foo.Bar.Baz获得,两种方式获取的效果是等价的。

NOTE: 本文仅讨论结构体中字段嵌入问题。嵌入也可以用于接口,一个接口内部可以嵌入其他接口。例如,io.ReadWriter由一个io.Reader和一个io.Writer组成。

type ReadWriter interface {
        Reader
        Writer
}

前面我们已分析了什么是类型嵌入,下面来看一个错误使用类型嵌入的例子。该例子将实现一个结构体保存一些内存中的数据,并且通过锁保护对它的并发访问。

type InMem struct {
        sync.Mutex
        m map[string]int
}
func New() *InMem {
        return &InMem{m: make(map[string]int)}
}

将结构体 InMem 中的map m定为小写,限制调用方直接操作m, 而是通过导出的方法进行操作。互斥锁以内嵌的形式存在(sync.Mutex), 获取结构体中数据的Get方法实现如下:

func (i *InMem) Get(key string) (int, bool) {
        i.Lock()
        v, contains := i.m[key]
        i.Unlock()
        return v, contains
}

由于互斥锁是嵌入的,我们可以直接从接收器i访问Lock和Unlock方法。前面说了这是一个错误的例子,错误在什么地方呢?由于sync.Mutex是嵌入类型,Lock和Unlock方法将被提升。因此,使用InMem的调用方也可以看到这两个方法. 这种由于内嵌导致的方法提升可能不是我们希望的,在大多数情况下,互斥锁是我们希望封装在结构体内部并且使外部客户端不可见的内容。因此,在这种情况下,不应该将其设置为嵌入字段。

m := inmem.New()
m.Lock() // ??

而应该是这样,调整为非嵌入字段。由于mu不可导出,它不能被外部客户端直接调用。

type InMem struct {
        mu sync.Mutex
        m map[string]int
}

现在来看另外一个例子,这次使用嵌入类型是一种正确的做法。例子描述的是实现一个自定义的日志记录功能,它包含一个io.WriteCloser 并对外暴露Write和Close两个方法。如果io.WriteCloser不是嵌入的,需要下面这样编码。

type Logger struct {
    writeCloser io.WriteCloser
}
func (l Logger) Write(p []byte) (int, error) {
    return l.writeCloser.Write(p)
}
func (l Logger) Close() error {
    return l.writeCloser.Close()
}
func main() {
    l := Logger{writeCloser: os.Stdout}
    _, _ = l.Write([]byte("foo"))
    _ = l.Close()
}

Logger必须提供一个Write和Close方法,然后调用writeCloser进行真正的Write和Close. 但是,如果将字段改为内嵌,就不用为Logger创建Write和Close方法,实现代码如下。

type Logger struct {
    io.WriteCloser
}
func main() {
    l := Logger{WriteCloser: os.Stdout}
    _, _ = l.Write([]byte("foo"))
    _ = l.Close()
}

从客户端角度看,调用方式没有任何差别,都是调用Write和Close方法。但是采用内嵌不用对Logger再做一层包装,Logger继承了io.WriteCloser的方法,所以Logger满足了io.WriteCloser接口。

NOTE:嵌入和OOP子类化,有人对嵌入和OOP子类化区分不清楚,这两者之间的主要区别是方法的接收者不同。下图左侧表示类型X嵌入在类型Y中,而右侧类型Y继承类型X。通过嵌入,Foo的接收者仍然是X而不是Y. 但是通过子类化,Foo的接收者是Y而不是X。Go中的嵌入是组合关系,而不是继承关系。详细分析参考(http://sd.jtimothyking.com/2018/06/25/subclassing-vs-embedding-in-golang/)

在这里插入图片描述

总结,对于类型嵌入,我们需要知道它不是必须的,这意味着无论什么时候,我们都可以在不使用类型嵌入的情况下解决问题。使用类型嵌入大多数情况下是为代码编写方便。如果决定使用类型嵌入,我们需要牢记下面两个原则:

  • 类型嵌入不应该仅用作一些语法糖来简化对字段的访问(例如调用Foo.Baz()而不是调用Foo.Bar.Baz()),如果这是唯一的目的,不用使用类型嵌入。

  • 类型嵌入不应该提升我们想要对外部隐藏的数据字段和行为方法。例如,像本文的例子,允许调用方访问应该对结构体保持私有的互斥锁。

NOTE:有些人会说,在可导出的结构体上使用类型嵌入可能会导致代码在维护方面需要付出额外的功夫。的确,将类型嵌入到导出的结构体中意味着在这种类型演变时保持小心。例如,如果我们向内部类型添加一个新方法,应该确保它不会破坏上面的第二个原则。因此,为了避免这种额外的工作,开发人员要防止将类型嵌入到公共结构体中。

牢记上述使用类型嵌入的原则,合理地使用类型嵌入可以帮助我们避免带有额外转发方法的代码(像上面的Logger中类型嵌入)。但是,不要为了用而用,不要将隐藏数据字段和方法约束丢给调用方,使用时要有充分的理由。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/417634.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

lv20 QT对话框3

1 内置对话框 标准对话框样式 内置对话框基类 QColorDialog, QErrorMessage QFileDialog QFontDialog QInputDialog QMessageBox QProgressDialogQDialog Class帮助文档 示例:各按钮激发对话框实现基类提供的各效果 第一步:实现组件布局&…

Redis 之三:发布订阅(pub/sub)

概念介绍 Redis 发布订阅 (pub/sub) 是一种消息通信模式,它允许客户端之间进行异步的消息传递 Redis 客户端可以订阅任意数量的频道。 模型中的角色 在该模型中,有三种角色: 发布者(Publisher):负责发送信…

【Hudi】核心概念

https://www.bilibili.com/video/BV1ue4y1i7na?p17&vd_sourcefa36a95b3c3fa4f32dd400f8cabddeaf 大数据新风口:Hudi数据湖(尚硅谷&Apache Hudi联合出品) 1 基础概念 1.1 时间轴(TimeLine) 1.2 文件布局(File Layout) 1.3 索引(In…

Unity-PDF分割器(iTextSharp)

PDF分割器 Unity-PDF分割器前言核心思路解决过程一、Unity安装iTextSharp二、运行时计算将要生成文件的大小三、分割核心代码四、使用StandaloneFileBrowser五、其他的一些脚本六、游戏界面主体的构建MainWindowWarningPanel & FinishPanel By-Round Moon Unity-PDF分割器 …

浅谈 Linux 网络编程 socket

文章目录 socket 介绍 socket 介绍 socket 被翻译成 网络套接字,这个名字实在是不好理解,我更愿意称为"插槽"。 忽略 socket 的中文名,先无脑记住两个规则: ① 记住,一个文件描述符(fd) 指向一个 socket&…

同芯.共赢 | 暴雨服务器亮相AMD EPYC合作伙伴峰会

2月29日,AMD EPYC合作伙伴峰会活动在北京成功举行,暴雨作为AMD重要生态合作伙伴应邀参加。作为AMD开年首场活动,此次活动意义非凡,AMD在现场向合作伙伴分享了AMD数据中心全新产品路线、解决方案以及生态建设领域的最新进展。 AMD是…

android开发平台,Java+性能优化+APP开发+NDK+跨平台技术

开头 通常作为一个Android APP开发者,我们并不关心Android的源代码实现,不过随着Android开发者越来越多,企业在筛选Android程序员时越来越看中一个程序员对于Android底层的理解和思考,这里的底层主要就是Android Framewok中各个组…

机器学习专项课程03:Unsupervised Learning, Recommenders, Reinforcement Learning笔记 Week02

Week 02 of Unsupervised Learning, Recommenders, Reinforcement Learning 课程地址: https://www.coursera.org/learn/unsupervised-learning-recommenders-reinforcement-learning 本笔记包含字幕,quiz的答案以及作业的代码,仅供个人学习…

二分查找讲解

关于我为什么要写单独开一篇文章写二分,实际上那么多困难的算法,比如线段树,并查集等等都没有难倒我,我最近却被二分难倒了,而且是两次,两次在赛场上做不出来二分的应用题,于是我决定写一篇二分查找的算法总结.刚接触算法的时候本来是要写一篇的,但后面因为各种原因搁置了,现在…

R语言数学建模(二)—— tidymodels

R语言数学建模(二)—— tidymodels 文章目录 R语言数学建模(二)—— tidymodels前言一、示例数据集二、拆分数据集2.1 拆分数据集的常用方法2.2 验证集2.3 多层次数据2.4 其他需考虑问题 三、parsnip用于拟合模型3.1 创建模型3.2 …

腾讯云优惠券领取的三个渠道,先领券再下单!

腾讯云代金券领取渠道有哪些?腾讯云官网可以领取、官方媒体账号可以领取代金券、完成任务可以领取代金券,大家也可以在腾讯云百科蹲守代金券,因为腾讯云代金券领取渠道比较分散,腾讯云百科txybk.com专注汇总优惠代金券领取页面&am…

禁止safari浏览器网页双击缩放功能

普通浏览器 普通浏览器&#xff0c;只需要增加meta标签禁止缩放功能就行了 <meta content"widthdevice-width, initial-scale1.0, maximum-scale1.0, user-scalable0;" name"viewport" /> user-scalableno或0 //禁止双指缩放页面initial-scale1.0…

企业文件图纸加密有哪些?图纸文件加密防泄密软件如何选?

在现在的市场发展中&#xff0c;对于企业的图纸文件安全问题越来越重视&#xff0c;如设计图纸&#xff0c;重要文件等&#xff0c;一旦泄漏就会给企业造成巨大的经济损失。所以对企业管理者来讲&#xff0c;如何才能选择一款好用的适合本企业的图纸文件加密软件是非常重要的&a…

【C++】手把手教你手搓模拟实现string类

前言 string类一直都是C的经典问题&#xff0c;之前的文章已经对string类做了一个基本的介绍&#xff08;string类的基本常用接口&#xff09;&#xff0c;为了更好理解string类的功能&#xff0c;此篇文章将手把手教你带你手搓模拟实现string类&#xff0c;快来一起学习吧&am…

Spring 事务传播机制

事务传播机制&#xff1a;多个事务⽅法存在调⽤关系时, 事务是如何在这些⽅法间进⾏传播的。 ⽐如&#xff1a;有两个⽅法A&#xff0c;B都被 Transactional 修饰,&#xff0c;A⽅法调⽤B⽅法 A⽅法运⾏时, 会开启⼀个事务。当A调⽤B时&#xff0c; B⽅法本⾝也有事务&#xf…

Pod 进阶

目录 资源限制 健康检查 资源限制 当定义 Pod 时可以选择性地为每个容器设定所需要的资源数量。 最常见的可设定资源是 CPU 和内存大小&#xff0c;以及其他类型的资源。 当为 Pod 中的容器指定了 request 资源时&#xff0c;代表容器运行所需的最小资源量&#xff0c;调度器…

打造去中心化透明储蓄罐:Solidity智能合约的又一实践

一、案例背景 传统的储蓄罐通常是由个人或家庭使用&#xff0c;用于存放硬币或小额纸币。然而&#xff0c;这样的储蓄罐缺乏透明性&#xff0c;用户无法实时了解储蓄情况&#xff0c;也无法确保资金的安全性。 通过Solidity智能合约&#xff0c;我们可以构建一个去中心化…

外汇天眼:外汇市场有哪些功能?

外汇市场有三个方面的功能&#xff0c;一是实现购买力的国际转移&#xff0c;二是提供资金融通&#xff0c;三是提供外汇保值和投机的市场机制。 实现购买力的国际转移 国际贸易和国际资金融通至少涉及到两种货币&#xff0c;而不同的货币对不同的国家形成购买力&#xff0c;…

02| JVM堆中垃圾回收的大致过程

如果一直在创建对象&#xff0c;堆中年轻代中Eden区会逐渐放满&#xff0c;如果Eden放满&#xff0c;会触发minor GC回收&#xff0c;创建对象的时GC Roots&#xff0c;如果存在于里面的对象&#xff0c;则被视为非垃圾对象&#xff0c;不会被此次gc回收&#xff0c;就会被移入…

逆向案例一:AES解密基于数位观察城市数据

import requests import json from Crypto.Cipher import AES # 开始解密 from Crypto.Util.Padding import unpad #去填充的逻辑 import base64 url https://app.swguancha.com/client/v1/cPublic/consumer/baseInfo data {current: 1,"dimensionTime": "20…