GO语言-09通过例子了解通过反射进行实例化

简介: GO语言的学习与记录,第九篇:通过一个例子了解通过反射进行实例化。内容用到了接口、结构体和JSON互转、反射的类型和实例化的内容

theme: healer-readable

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第10天,点击查看活动详情

初心是记录和总结,自己学习Go语言的历程。如果能帮助到你,这是我的荣幸。

反射 - 万变不离其宗

反射能够将我们的程序一下子变得高大上!通过反射可以在程序运行期对程序本身进行访问和修改的能力。最最常见的一个例子就是接收用户传过来的值,将值赋给响应的变量。这种从值 -> 类型的感觉就是反射。举一个对比的例子,之前写程序都是:

var x int = 8

这种都是静态的,定死的变量。在程序运行过程中,永远不会被改变,难道你写程序都是写Demo这种重复且重复的事情吗!!当然不是(虽然学习过程中一直在做这种事情,哈哈)。

那么,B/S架构的程序,现在常用的数据传输是用户通过浏览器输入数据,前端使用JSON作为数据的传输格式,设想一下,我们接收到的不确定的值,如何对它进行封装呢?

结构体 + 反射的案例

俺们知道结构体这种类型的变量,作为多种类型的复合产品,最适合来形容物体多属性的特征。这里比如说还是:

小程在一家宠物店填写了 和狗子: 小黑的信息,要求宠物店后端将收到的 Json进行封装,并输出。

【前提知识1】了解一下JSON和结构体互转

这种都是调用他人的函数库,就不详细介绍啦,了解用法就行。这里使用的是 encoding/json,通过 json. 的方式,调用Marshal(转json)或Unmarshal(json转结构体)

结构体转JSON

buf, err := json.Marshal(定义好的结构体变量) //转换为json返回两个结果
if err != nil {
   // 发现错误打印错误,并返回
   fmt.Println("err = ", err)
   return
}
// 输出结果
fmt.Println("json = ", string(buf))

JSON转结构体

// 定义结构体变量
var res 结构体
// 传入结构体变量指针作为第二个参数传入
if err := json.Unmarshal([]byte(jsonstr), &res); err != nil {
   // 发现错误打印错误
   fmt.Println(err)
}
// 输出结果
fmt.Println(res)

【前提知识2】结构体取类型+实例化的方法

反射用的是 reflect内置函数库,我们先了解一下取类型和实例化
tp := reflect.TypeOf(x) // 获取到x的类型对象Type
instance := reflect.New(tp) //通过Type获得该类型的值类型反射接口interface

【正篇】定义结构体

type Person struct {
   Name string `json:"name"`
   Age  int    `json:"age"`
}

type Dog struct {
   Name  string `json:"name"`
   Color string `json:"color"`
}

讲解:结构体转义成json的时候,保持名称小写会让程序看的清爽,\`json:"name"\`,这种用法就是表示在json转义的时候名称按照name这个值来。

【正篇】准备JSON语句模拟我们的数据

func main() {
   personJson := `{"name":"程云来","age":23}`
   dogJson := `{"name":"小黑","color":"黑棕色"}`
}

这个时候,我们最容易想到的是,直接定义两个类型变量,然后通过上面学的JSON和结构体互转的方式,直接转义:

直接定义对象通过方法转义

func main() {
   personJson := `{"name":"程云来","age":23}`
   dogJson := `{"name":"小黑","color":"黑棕色"}`
   

    var person Person
    var dog Dog

    if err := json.Unmarshal([]byte(personJson), &person); err != nil {
       fmt.Println(err)
    }
    fmt.Println(person)

    if err := json.Unmarshal([]byte(dogJson), &dog); err != nil {
       fmt.Println(err)
    }
    fmt.Println(dog)

}

等等,这重复代码太多了吧,而且代码显得不好看,能不能思考一下,通过这个对象调用什么方法的方式就能完成json.Unmarshal

【正篇】加上接口

如果,我们通过Person{}.toStruct的方式,我们就能完成JSON转结构体的动作,这会美化我们的代码,而每个类的toStruct方法应该抽取,利用反射的技术将JSON转结构体的方法,

type JsonToStruct interface {
   toStruct()
}

// 2.实现接口,实现接口中应该将具体的封装操作通过反射的方式抽取出来,避免重复的代码
func (Person) toStruct(jsonStr string) Person {
    // 调用反射的技术
}

func (Dog) toStruct(jsonStr string) Dog {
    // 调用反射的技术
}

【正篇】利用反射完成JSON转结构体

观察JSON转结构体的方法语句:json.Unmarshal([]byte(jsonstr), &res),我们可以发现这里有两个变量:

  • json语句变量:jsonstr
  • 实际对象的指针值

所以方法也应该接收该两个变量,由于我们抽取出两者的共性,而对象类型是不唯一的,所以对象类型接收使用空接口,调用反射的技术实例化对象。

func newInstance(x interface{}, jsonStr string) interface{} {
   tp := reflect.TypeOf(x)
   instance := reflect.New(tp).Interface()
   if err := json.Unmarshal([]byte(jsonStr), &instance); err != nil {
      fmt.Println(err)
   }
   return instance
}

小坑注意!

instance := reflect.New(tp).Interface()该语句为什么要这样写,通过文档得知New方法返回的是Value

image.png
这个Value是Go语言值的反射接口,它并不是一个类型的对象,所以不能直接将范围值使用,解决办法是在后面再调用Interface(),我们也通过文档看看

image.png
它相当于:
Var i interface{} = (v的底层值)

表示使用了一个空接口,接收了New方法实例化的实际对象

【正篇】完善实现接口

func (Person) toStruct(jsonStr string) Person {
   instance := newInstance(Person{}, jsonStr)
   person := instance.(*Person)
   return *person
}

func (Dog) toStruct(jsonStr string) Dog {
   instance := newInstance(Dog{}, jsonStr)
   dog := instance.(*Dog)
   return *dog
}

由于空接口是一种指针类型的数据,所以断言的时候需要加上*Person

完整代码附上

package main

import (
   "encoding/json"
   "fmt"
   "reflect"
)

type Person struct {
   Name string `json:"name"`
   Age  int    `json:"age"`
}

type Dog struct {
   Name  string `json:"name"`
   Color string `json:"color"`
}

// 这里例子是模拟最底层的封装结构体,假设前端传入了一段json字符串,我们需要将它转为对应的struct
func main() {
   personJson := `{"name":"程云来","age":23}`
   dogJson := `{"name":"小黑","color":"黑棕色"}`

   person := Person{}.toStruct(personJson)
   dog := Dog{}.toStruct(dogJson)
   fmt.Println(person)
   fmt.Println(dog)

}

// 发现封装json的代码重复性太高了,进行改造,我们可以考虑封装一个结构体的方法,通过它接收一个json字符串后然后对其进行封装。
// 1.定义接口
type JsonToStruct interface {
   toStruct()
}

// 2.实现接口,实现接口中应该将具体的封装操作通过反射的方式抽取出来,避免重复的代码
func (Person) toStruct(jsonStr string) Person {
   instance := newInstance(Person{}, jsonStr)
   person := instance.(*Person)
   return *person
}

func (Dog) toStruct(jsonStr string) Dog {
   instance := newInstance(Dog{}, jsonStr)
   dog := instance.(*Dog)
   return *dog
}

// 3.将封装的语句抽取封装
func newInstance(x interface{}, jsonStr string) interface{} {
   tp := reflect.TypeOf(x)
   instance := reflect.New(tp).Interface()
   if err := json.Unmarshal([]byte(jsonStr), &instance); err != nil {
      fmt.Println(err)
   }
   return instance
}

// 4.优化
type GetType interface {
   toStruct()
}
目录
相关文章
|
9天前
|
监控 算法 Go
Golang深入浅出之-Go语言中的服务熔断、降级与限流策略
【5月更文挑战第4天】本文探讨了分布式系统中保障稳定性的重要策略:服务熔断、降级和限流。服务熔断通过快速失败和暂停故障服务调用来保护系统;服务降级在压力大时提供有限功能以保持整体可用性;限流控制访问频率,防止过载。文中列举了常见问题、解决方案,并提供了Go语言实现示例。合理应用这些策略能增强系统韧性和可用性。
40 0
|
1天前
|
存储 编译器 Go
Go语言学习12-数据的使用
【5月更文挑战第5天】本篇 Huazie 向大家介绍 Go 语言数据的使用,包含赋值语句、常量与变量、可比性与有序性
37 6
Go语言学习12-数据的使用
|
3天前
|
Java Go
一文带你速通go语言指针
Go语言指针入门指南:简述指针用于提升效率,通过地址操作变量。文章作者sharkChili是Java/CSDN专家,维护Java Guide项目。文中介绍指针声明、取值,展示如何通过指针修改变量值及在函数中的应用。通过实例解析如何使用指针优化函数,以实现对原变量的直接修改。作者还邀请读者加入交流群深入探讨,并鼓励关注其公众号“写代码的SharkChili”。
9 0
|
3天前
|
存储 缓存 Java
来聊聊go语言的hashMap
本文介绍了Go语言中的`map`与Java的不同设计思想。作者`sharkChili`是一名Java和Go开发者,同时也是CSDN博客专家及JavaGuide项目的维护者。文章探讨了Go语言`map`的数据结构,包括`count`、`buckets指针`和`bmap`,解释了键值对的存储方式,如何利用内存对齐优化空间使用,并展示了`map`的初始化、插入键值对以及查找数据的源码过程。此外,作者还分享了如何通过汇编查看`map`操作,并鼓励读者深入研究Go的哈希冲突解决和源码。最后,作者提供了一个交流群,供读者讨论相关话题。
14 0
|
4天前
|
Java Go
Go语言学习11-数据初始化
【5月更文挑战第3天】本篇带大家通过内建函数 new 和 make 了解Go语言的数据初始化过程
17 1
Go语言学习11-数据初始化
|
4天前
|
自然语言处理 安全 Java
速通Go语言编译过程
Go语言编译过程详解:从词法分析(生成token)到句法分析(构建语法树),再到语义分析(类型检查、推断、匹配及函数内联)、生成中间码(SSA)和汇编码。最后,通过链接生成可执行文件。作者sharkchili,CSDN Java博客专家,分享技术细节,邀请读者加入交流群。
22 2
|
5天前
|
Java Linux Go
一文带你速通Go语言基础语法
本文是关于Go语言的入门介绍,作者因其简洁高效的特性对Go语言情有独钟。文章首先概述了Go语言的优势,包括快速上手、并发编程简单、设计简洁且功能强大,以及丰富的标准库。接着,文章通过示例展示了如何编写和运行Go代码,包括声明包、导入包和输出语句。此外,还介绍了Go的语法基础,如变量类型(数字、字符串、布尔和复数)、变量赋值、类型转换和默认值。文章还涉及条件分支(if和switch)和循环结构(for)。最后,简要提到了Go函数的定义和多返回值特性,以及一些常见的Go命令。作者计划在后续文章中进一步探讨Go语言的其他方面。
10 0
|
6天前
|
JavaScript 前端开发 Go
Go语言的入门学习
【4月更文挑战第7天】Go语言,通常称为Golang,是由Google设计并开发的一种编程语言,它于2009年公开发布。Go的设计团队主要包括Robert Griesemer、Rob Pike和Ken Thompson,这三位都是计算机科学和软件工程领域的杰出人物。
14 1
|
6天前
|
Go
|
7天前
|
分布式计算 Java Go
Golang深入浅出之-Go语言中的分布式计算框架Apache Beam
【5月更文挑战第6天】Apache Beam是一个统一的编程模型,适用于批处理和流处理,主要支持Java和Python,但也提供实验性的Go SDK。Go SDK的基本概念包括`PTransform`、`PCollection`和`Pipeline`。在使用中,需注意类型转换、窗口和触发器配置、资源管理和错误处理。尽管Go SDK文档有限,生态系统尚不成熟,且性能可能不高,但它仍为分布式计算提供了可移植的解决方案。通过理解和掌握Beam模型,开发者能编写高效的数据处理程序。
135 1