golang 实现 deepcopy 的两种实现方式

简介: golang 实现 deepcopy 的两种实现方式

golang deepcopy 的两种实现方式

最近在基于 gin 封装 rum-gonic - github web 框架的过程中,遇到了一个问题。

在注册路由的时候传递是 指针对象, 因此造成所有的 request 请求使用相同的 CreateUser 对象, 出现并发冲突。

func init() {
    RouterGroup_User.Register(&CreateUser{})
}

type CreateUser struct {
    httpx.MethodPost `path:""`
    Name     string `query:"name"`
    Password string `query:"password"`
}

struct 结构体 deepcopy 的实现

基于 sturct 的实现, 由于有 明确struct 对象结构, 通常直接创建一个全新对象, 同时把老数据复制进去。

例如 gin-gonic 的 Copy() 方法

// Copy returns a copy of the current context that can be safely used outside the request's scope.
// This has to be used when the context has to be passed to a goroutine.
func (c *Context) Copy() *Context {
    cp := Context{
        writermem: c.writermem,
        Request:   c.Request,
        Params:    c.Params,
        engine:    c.engine,
    }
    cp.writermem.ResponseWriter = nil
    cp.Writer = &cp.writermem
    cp.index = abortIndex
    cp.handlers = nil
    cp.Keys = map[string]interface{}{}
    for k, v := range c.Keys {
        cp.Keys[k] = v
    }
    paramCopy := make([]Param, len(cp.Params))
    copy(paramCopy, cp.Params)
    cp.Params = paramCopy
    return &cp
}

interface 接口 deepcopy 的实现

对于 接口 interface{} 就稍微麻烦一点了。 由于 接口 是一组方法的集合, 也就意味着

  1. 接口的 底层结构体 是不定的。
  2. 无法直接获取 底层结构体 的字段数据。

这时可以通过使用 反射 reflect.New() 创建对象。

mohae/deepcopy - github 就是使用的这种方式

deepcopy 库中一样通过 反射递归 实现复制, 是为了兼容更多的情况。
而在自己实现编码的时候, 大部分情况的是可控的, 实现方式可以适当简化, 不用与 deepcopy 完全相同。

1. 通过反射创建零值接口对象

func deepcoper(op Operator) Operator {
    // 1. 获取 反射类型
    rt := reflect.TypeOf(op)

    // 2. 获取真实底层结构体的类型
    rtype := deRefType(rt)

    // 3. reflect.New() 创建反射对象,并使用 Interface() 转真实对象
    opc := reflect.New(rtype).Interface()

    // 4. 断言为 operator
    return opc.(Operator)
}

func deRefType(typ reflect.Type) reflect.Type {
    for typ.Kind() == reflect.Ptr {
        typ = typ.Elem()
    }

    return typ
}

需要注意的是, 通过上述方式创建的出来的新对象

  1. 依然 不知道新接口底层结构体是什么, 也并不需要关心, 接口中心本身就是在 相同的方法实现 上。
  2. 接口底层结构体中所有字段值为 零值, 可能需要必要的初始化,否则直接使用可能 panic, 例如结构体中存在 指针类型对象

通常这种情况, 在自己写代码的时候,可以增加一个 初始化方法

2. 使用接口断言进行初始化

在实现了初始化方法之后, 可以再定义一个接口。 通过断言转化为新接口, 调用初始化方法。

func deepcoper(op Operator) Operator {
    rt := reflect.TypeOf(op)

    rtype := deRefType(rt)

    opc := reflect.New(rtype).Interface()

    // 3.1. 使用断言转化新接口, 初始化底层对象
    if opcInit, ok := opc.(OperatorIniter); ok {
        opcInit.SetDefaults()
    }

    return opc.(Operator)
}

type OperatorIniter interface {
    SetDefaults()
}

3. 使用反射调用方法进行初始化

在不增加新接口的情况下, 可以在反射创建的过程中 判断初始化方法的存在, 并调用 进行初始化。


func deepcoper(op Operator) Operator {
    rt := reflect.TypeOf(op)
    rtype := deRefType(rt)
    ropc := reflect.New(rtype)

    // 3.2 使用反射 call method 初始化
    method := ropc.MethodByName("SetDefaults")
    if method.IsValid() && !method.IsZero() {
        method.Call(nil)
    }

    opc := ropc.Interface()
    return opc.(Operator)
}

搞点看起来高级的: 接口初始化工厂

上述代码中都是直接对接口对象进行的操作。 搞一个 struct 创建并初始化接口, 可以携带和组织更多的信息。

func NewOperatorFactory(op Operator) *OperatorFactory {
    opfact := &OperatorFactory{}

    opfact.Type = deRefType(reflect.TypeOf(op))
    // opfact.Operator = op
    
    return opfact
}

type OperatorFactory struct {
    Type     reflect.Type
    Operator Operator
}

func (o *OperatorFactory) New() Operator {

    oc := reflect.New(o.Type).Interface().(Operator)

    return oc
}
相关文章
Golang:deepcopy深拷贝工具库
Golang:deepcopy深拷贝工具库
641 0
|
3月前
|
Go
Golang语言之管道channel快速入门篇
这篇文章是关于Go语言中管道(channel)的快速入门教程,涵盖了管道的基本使用、有缓冲和无缓冲管道的区别、管道的关闭、遍历、协程和管道的协同工作、单向通道的使用以及select多路复用的详细案例和解释。
126 4
Golang语言之管道channel快速入门篇
|
3月前
|
Go
Golang语言文件操作快速入门篇
这篇文章是关于Go语言文件操作快速入门的教程,涵盖了文件的读取、写入、复制操作以及使用标准库中的ioutil、bufio、os等包进行文件操作的详细案例。
70 4
Golang语言文件操作快速入门篇
|
3月前
|
Go
Golang语言之gRPC程序设计示例
这篇文章是关于Golang语言使用gRPC进行程序设计的详细教程,涵盖了RPC协议的介绍、gRPC环境的搭建、Protocol Buffers的使用、gRPC服务的编写和通信示例。
108 3
Golang语言之gRPC程序设计示例
|
3月前
|
安全 Go
Golang语言goroutine协程并发安全及锁机制
这篇文章是关于Go语言中多协程操作同一数据问题、互斥锁Mutex和读写互斥锁RWMutex的详细介绍及使用案例,涵盖了如何使用这些同步原语来解决并发访问共享资源时的数据安全问题。
95 4
|
3月前
|
Go
Golang语言错误处理机制
这篇文章是关于Golang语言错误处理机制的教程,介绍了使用defer结合recover捕获错误、基于errors.New自定义错误以及使用panic抛出自定义错误的方法。
51 3
|
3月前
|
Go 调度
Golang语言goroutine协程篇
这篇文章是关于Go语言goroutine协程的详细教程,涵盖了并发编程的常见术语、goroutine的创建和调度、使用sync.WaitGroup控制协程退出以及如何通过GOMAXPROCS设置程序并发时占用的CPU逻辑核心数。
59 4
Golang语言goroutine协程篇
|
3月前
|
Prometheus Cloud Native Go
Golang语言之Prometheus的日志模块使用案例
这篇文章是关于如何在Golang语言项目中使用Prometheus的日志模块的案例,包括源代码编写、编译和测试步骤。
70 3
Golang语言之Prometheus的日志模块使用案例
|
3月前
|
Go
Golang语言之函数(func)进阶篇
这篇文章是关于Golang语言中函数高级用法的教程,涵盖了初始化函数、匿名函数、闭包函数、高阶函数、defer关键字以及系统函数的使用和案例。
67 3
Golang语言之函数(func)进阶篇
|
2月前
|
前端开发 中间件 Go
实践Golang语言N层应用架构
【10月更文挑战第2天】本文介绍了如何在Go语言中使用Gin框架实现N层体系结构,借鉴了J2EE平台的多层分布式应用程序模型。文章首先概述了N层体系结构的基本概念,接着详细列出了Go语言中对应的构件名称,包括前端框架(如Vue.js、React)、Gin的处理函数和中间件、依赖注入和配置管理、会话管理和ORM库(如gorm或ent)。最后,提供了具体的代码示例,展示了如何实现HTTP请求处理、会话管理和数据库操作。
33 0