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.rhkb.cn/news/266126.html

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

相关文章

Leetcode1035. 不相交的线 -代码随想录

题目&#xff1a; 代码(首刷看解析 2024年2月29日&#xff09;&#xff1a; class Solution { public:// 动态规划 思路同最长公共子序列int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {int m nums1.size();int n nums2.size();vecto…

vue中组合式API和选项式API的区别

组合式api&#xff08;Composition API&#xff09;是vue3对我们开发者来说变化非常大的更新&#xff0c;我们先不关注具体语法&#xff0c;先对它有一个大的感知。 通过vue2, vue3两种形式实现同一个需求&#xff0c;理解vue3的compition api 带来的好处 两个独立的功能&…

MySQL 如何从 Binlog 找出变更记录并回滚

文章目录 前言1. 案例模拟1.1 确认信息1.2 下载 Binlog1.3 准备环境1.4 注册 Binlog1.5 准备结构信息1.6 Python 订阅1.7 输出结果展示 2. 原理解析2.1 程序设计2.2 模块版本 总结 前言 最近有研发同学问我&#xff1a;有一个问题&#xff0c;想查一个 ID 为 xxxx 的 sku 什么…

Android Gradle开发与应用 (二) : Groovy基础语法

1. Groovy是什么 Groovy是基于JVM虚拟机的一种动态语言&#xff0c;语法和Java非常相似&#xff0c;并能够无缝地与Java代码集成和互操作&#xff0c;增加了很多动态类型和灵活的特性。(闭包、DSL) 语法和Java非常相似这个特点&#xff0c;意味着&#xff0c;如果我们完全不懂…

推荐几款优秀免费开源的导航网站

&#x1f9a9;van-nav 项目地址&#xff1a;van-nav项目介绍&#xff1a;一个轻量导航站&#xff0c;汇总你的所有服务。项目亮点&#xff1a;全平台支持&#xff0c;单文件部署&#xff0c;有配套浏览器插件。效果预览 &#x1f9a9;发现导航 项目地址&#xff1a;nav项目…

算法 -【从前序与中序遍历序列构造二叉树】

从前序与中序遍历序列构造二叉树 题目示例1示例2 分析代码 题目 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例1 输入: preorder [3,9,20,1…

RabbitMQ实战学习

RabbitMQ实战学习 文章目录 RabbitMQ实战学习RabbitMQ常用资料1、安装教程2、使用安装包3、常用命令4、验证访问5、代码示例 一、RabbitMQ基本概念1.1. MQ概述1.2 MQ 的优势和劣势1.3 MQ 的优势1. 应用解耦2. 异步提速3. 削峰填谷 1.4 MQ 的劣势1.5 RabbitMQ 基础架构1.6 JMS 二…

怎样才能考上南京大学的计算机研究生?

附上南大与同层次学校近四年的分数线对比&#xff0c;整体很难 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 我本人是双非一本的计算机专业&#xff0c;23考研一战上岸的&#xf…

【探索AI】探索未来-计算机专业必看的几部电影

计算机专业必看的几部电影 计算机专业必看的几部电影&#xff0c;就像一场精彩的编程盛宴&#xff01;《黑客帝国》让你穿越虚拟世界&#xff0c;感受高科技的魅力&#xff1b;《社交网络》揭示了互联网巨头的创业之路&#xff0c;《源代码》带你穿越时间解救世界&#xff0c;…

我写了个ImageWindow应用

文章目录 0 引言1 应用简介2 主要功能和特点2.1 多图像同/异步像素级对比2.2 支持多达30种图像格式2.3 高效率的图像处理性能 3 简明使用教程3.1 软件下载安装与更新3.1.1 软件下载与安装3.1.2 软件更新 3.2 多视窗添加并自动最优排列3.3 多样化图像导入方式3.4 自动切换显示模…

MS1100——16-bit 内置基准模数转换器,可替代ADS1100

产品简述 MS1100 是一款高精度 16bit 模数转换器。内部集成 2.048V 基 准源&#xff0c;差分输入范围达到 2.048V 。使用了 I 2 C 兼容接口。电源电 压范围为 2.7V 到 5.5V 。 MS1100 转换速率为 15 、 30 、 60 或 240SPS &#xff0c;集成有可编程增 益放…

【Web安全靶场】sqli-labs-master 21-37 Advanced-Injection

sqli-labs-master 21-37 Advanced-Injection 第一关到第二十关请见专栏 文章目录 sqli-labs-master 21-37 Advanced-Injection第二十一关-Cookie注入第二十二关-Cookie注入第二十三关-注释符过滤的报错注入第二十四关-二次注入第二十五关-过滤OR、AND双写绕过第二十五a关-过滤…

如何在Window系统部署BUG管理软件并结合内网穿透实现远程管理本地BUG

文章目录 前言1. 本地安装配置BUG管理系统2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射本地服务3. 测试公网远程访问4. 配置固定二级子域名4.1 保留一个二级子域名5.1 配置二级子域名6. 使用固定二级子域名远程 前言 BUG管理软件,作为软件测试工程师的必备工具之一。在…

数据结构--树的遍历

数据结构--树的遍历 1. 前序中序后序遍历2. 前序中序后序遍历代码 1. 前序中序后序遍历 2. 前序中序后序遍历代码 /** public class TreeNode {int val 0;TreeNode left null;TreeNode right null;public TreeNode(int val) {this.val val;}} */// 前序遍历顺序&#xff1…

vue中使用echarts绘制双Y轴图表时,刻度没有对齐的两种解决方法

文章目录 1、原因2、思路3、解决方法3.1、使用alignTicks解决3.2、结合min和max属性去配置interval属性1、首先固定两边的分隔的段数。2、结合min和max属性去配置interval。 1、原因 刻度在显示时&#xff0c;分割段数不一样&#xff0c;导致左右的刻度线不一致&#xff0c;不…

GPT 的基础 - T(Transformer)

我们知道GPT的含义是&#xff1a; Generative - 生成下一个词 Pre-trained - 文本预训练 Transformer - 基于Transformer架构 我们看到Transformer模型是GPT的基础&#xff0c;这篇博客梳理了一下Transformer的知识点。 BERT: 用于语言理解。&#xff08;Transformer的Encoder…

Redis 在 Linux 系统下安装部署的两种方式详细说明

小伙伴们好&#xff0c;欢迎关注&#xff0c;一起学习&#xff0c;无限进步 Redis安装和配置 1、首先在官网下载好redis-6.0.9.tar.gzhttp://redis.io/ 或者使用 wget 命令下载&#xff1a;wget http://download.redis.io/releases/redis-6.0.9.tar.gz 2、下载使用上传到阿里…

vue使用gitshot生成gif

vue使用gitshot生成gif 问题背景 本文将介绍vue中使用gitshot生成gif。 问题分析 解决思路&#xff1a; 使用input组件上传一个视频&#xff0c;获取视频文件后用一个video组件进行播放&#xff0c;播放过程进行截图生成图片数组。 demo演示上传一个视频&#xff0c;然后生…

【InternLM 实战营笔记】大模型评测

随着人工智能技术的快速发展&#xff0c; 大规模预训练自然语言模型成为了研究热点和关注焦点。OpenAI于2018年提出了第一代GPT模型&#xff0c;开辟了自然语言模型生成式预训练的路线。沿着这条路线&#xff0c;随后又陆续发布了GPT-2和GPT-3模型。与此同时&#xff0c;谷歌也…

微服务之qiankun主项目+子项目搭建

主项目使用history&#xff0c;子项目使用hash模式 1. 下载安装"qiankun": "^2.10.13"2. 手动调用qiankun,使用vue脚手架搭建的项目1. 主项目配置&#xff08;我使用的是手动调用乾坤&#xff0c;在指定页面显示内容&#xff09;1. 要使用的页面中引入乾坤…