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 {ReaderWriter
}

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

type InMem struct {sync.Mutexm 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.Mutexm 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中类型嵌入)。但是,不要为了用而用,不要将隐藏数据字段和方法约束丢给调用方,使用时要有充分的理由。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TypeScript 中,接口(interface)可以描述几种不同类型的属性和结构

在 TypeScript 中,接口(interface)可以描述几种不同类型的属性和结构。以下是你可以在接口中定义的一些常见属性和结构: 属性签名 - 描述对象属性的名称和类型。 interface Person {name: string;age: number; }方法签名 - 描述函…

Linux之定时任务02

一、什么是crond Linux 中 crond 就是定时任务,即根据 crond 指定的时间,由系统按指定的时间,周期性,自动触发的事件。 crond 服务在默认的情况下会每分钟检查系统中是否有定时任务,如果有且符合触发条件,…

sql中如何实现递归

在SQL中,递归通常是通过使用公用表表达式(Common Table Expressions,CTE)来实现的。CTE允许你定义一个临时的结果集,该结果集可以在一个SELECT、INSERT、UPDATE或DELETE语句的主体中被引用。 递归CTE有两个关键部分&a…

vue前端使用get方式获取api接口数据 亲测

// GET请求示例 axios.get(‘http://127.0.0.1:5005/ReadIDCardInfo’) // 将URL替换为真正的API接口地址 .then(response > { if(response.data.code1){ var jsonDataresponse.data.data; console.log(jsonData); // 输出从API接口返回的数据 } }) .catch(error > { con…

MySQL(基础篇)——事务

一.事务简介 事务是一组操作的集合,他是一个不可分割的单位,事务会把所有的操作作色一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。 默认MySQL的事务是自动提交的,也就是说&#xff0c…

VS Code常用快捷键

前言 对于开发者而言,熟悉快捷键的使用,能够起到事半功倍的作用,提高工作效率。以下是我整理的一份VS Code常用快捷键清单,希望能够帮助到你,欢迎在评论区留下你的常用快捷键🤞。 设置VS Code中的键盘快捷…

抖音视频评论提取软件|视频数据批量采集工具

抖音视频评论批量下载软件是一款基于C#开发的高效工具,旨在帮助用户快速获取抖音视频评论数据。无论您是市场分析师、社交媒体管理者还是数据研究人员,这款软件都会成为您工作中不可或缺的利器。 软件的关键功能包括: 关键词搜索&#xff1…

AI智能电销机器人效果怎么样?呼叫部署

我们的生活早已变得无处不智能,从智能手机到无人车、虚拟VR到智能家居,你迎接的每一个清晨、享受的每一个夜晚,可能都离不开智能设备的服务。 工作中,智能化产业也正在影响着企业,电销机器人正在帮助各大企业获得更高的…

【Unity】如何设置Unity脚本的执行顺序?

在 Unity 编辑器中设置脚本执行顺序 在 Unity 中,如果有多个脚本,并且它们之间的执行顺序很重要,可以通过编辑器设置来确保它们按照自己期望的顺序执行。这对于确保某些脚本在其他脚本之前执行非常有用。在这篇文章中,将向会展示如…

2024年腾讯云十大优惠活动汇总(附云服务器价格表)

腾讯云服务器多少钱一年?62元一年起,2核2G3M配置,腾讯云2核4G5M轻量应用服务器218元一年、756元3年,4核16G12M服务器32元1个月、312元一年,8核32G22M服务器115元1个月、345元3个月,腾讯云服务器网txyfwq.co…

Stream流详解

当我们对一个集合中的元素进行多次过滤应该怎样做? 下面看一个案例 按照下面的要求完成集合的创建和遍历 创建一个集合,存储多个字符串元素 把集合中所有以"张"开头的元素存储到一个新的集合 把"张"开头的集合中的长度为3的元素存储到一个新…

金三银四,自动化测试面试题精选【美团二面】

面试一般分为技术面和hr面,形式的话很少有群面,少部分企业可能会有一个交叉面,不过总的来说,技术面基本就是考察你的专业技术水平的,hr面的话主要是看这个人的综合素质以及家庭情况符不符合公司要求,一般来…

Redis 消息队列:构建消息代理的 4 个简单步骤

消息代理是一种使系统、应用程序和服务能够通信和交换信息的软件。它在正式消息传递协议之间转换消息,并允许相互依赖的服务直接“对话”,即使是用不同语言编写或在不同平台上实现也是如此。在微服务中使用异步通信时,通常会使用消息代理。 消息代理可确保可靠且稳定的通信,…

golang 泛型详解

目录 概念 ~int vs .int 常见的用途和错误 结论: 概念 Go 在1.18 中添加了泛型,这样Go 就可以在定义时不定义类型,而是在使用时进行类型的定义,并且还可以在编译期间对参数类型进行校验。Go 目前只支持泛型方法,还…

Machine Vision Technology:Lecture2 Linear filtering

Machine Vision Technology:Lecture2 Linear filtering Types of ImagesImage denoising图像去噪Defining convolution卷积的定义Key properties卷积的关键属性卷积的其它属性Annoying details卷积练习Sharpening锐化Gaussian KernelNoise噪声 分类Gaussian noise高…

Unity3D UnlitShader模板详解

前言 Unity3D UnlitShader模板是一种非常基础的着色器模板,它不考虑光照和阴影等效果,只关注物体的颜色和纹理。在一些简单的游戏或者特效中,使用UnlitShader可以提高渲染效率,同时也能够实现一些特殊的效果,比如描边、…

AI大模型的发展趋势?

大模型的发展趋势主要体现在以下几个方面: 1、模型规模的增长: 随着数据量和计算能力的不断增加,大型模型的规模也在不断扩大。模型参数数量、层数等指标不断刷新,以应对更复杂的任务和更大规模的数据。 2、多模态融合&#xff…

HTTP详解(HTTP的特点,状态码,工作原理,GET和POST的区别,如何解决无状态通信)!!!

文章目录 一、HTTP协议简介二、HTTP的主要特点三、HTTP之URL四、Request和Respons五、HTTP的状态码六、HTTP工作原理七、GET和POST请求的区别八、解决HTTP无状态通信——Cookie和Session 一、HTTP协议简介 HTTP协议是Hyper Text Transfer Protocol(超文本传输协议&…

iOS App冷启动优化:Before Main阶段

iOS应用冷启动时,在 UIApplicationMain(argc, argv, nil, appDelegateClassName)方法执行前,主要经历以下阶段: 1. 执行exec()启动应用程序进程 2. 加载可执行文件,即将应用程序的Mach-O文件加载到内存…

WindowManagerService的addWindow方法源码解读

源码链接: https://cs.android.com/android/platform/superproject/main//main:frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java;l9516?qsanitizeWindowType&hlzh-cn addWindow 方法如下: public int addWind…