在Go中封装结构体

7

我刚开始学习Go语言。据我所知,Go语言的封装是基于包(package)级别的。我有一个简单的web控制器使用案例。我有一个结构体,作为JSON对象传入,并被解码为该结构体类型。

type User struct{
    Name String `json:"name"`
    //Other Variables
}

现在可以通过json.Unmarshal([]byte)将json转换为用户结构类型。但是,其他包也可以访问此用户结构。如何确保其他包只能访问与用户相关的方法。
我能想到的一个解决方案是:
type User struct{
    name String
}

type UserJSON struct{
    Name String `json:"name"`
}

func DecodeJSONToUser(rawJSON []byte) (User,error) {
    var userJSON UserJSON
    err := json.Unmarshal(rawJSON,&userJSON)
    //Do error handling
    return User{name:userJSON.Name},nil
}

有没有一种GO式的方法可以达到这一目的?

1
是的,有一种方法可以防止其他包使用你不想被使用的结构字段,那就是不导出它们。 - Himanshu
@GrzegorzŻur,我希望其他的包可以在用户结构上工作,例如调用User.GetName()而不是直接调用User.Name。 - rhumbaJi
@Himanshu 是的,但考虑这种情况,结构体User的每个成员都在JSON中出现,因此json.Unmarshal需要导出所有结构体字段。现在,谁能阻止像User.Name =“some_other_name”这样的操作呢? - rhumbaJi
创建一个包,将结构体分配在其中,对数据进行反序列化,并且不要在主程序中导入该包,这样除了该包之外,没有其他用户可以修改该结构体。将你的结构体封装在一个包内。 - Himanshu
1个回答

13
您可以使用一个具有公共字段的“包内结构体”来实现该结构体在包外不可见。接着,您可以使该结构体满足某个公共接口,从而实现完美解耦:
package user

import "encoding/json"

type User interface {
    Name() string
}

type user struct {
    Username string `json:"name"`
}

func (u *user) Name() string {
    return "Mr. " + u.Username
}

func ParseUserData(data []byte) (User, error) {
    user := &user{}
    if err := json.Unmarshal(data, user); err != nil {
        return nil, err
    }
    return user, nil
}

以及相应的测试:

package user_test

import (
    "testing"

    "github.com/teris-io/user"
)

func TestParseUserData(t *testing.T) {
    data := []byte("{\"name\": \"Uncle Sam\"}")
    expected := "Mr. Uncle Sam"
    if usr, err := user.ParseUserData(data); err != nil {
        t.Fatal(err.Error())
    } else if usr.Name() != expected {
        t.Fatalf("expected %s, found %s", expected, usr.Name())
    }
}

➜ 用户 git:(主分支) ✗ go test github.com/teris-io/user

好 github.com/teris-io/user 0.001秒

在解码后,您还可以将包本地对象转换为一些公共对象。

注意:其中一条评论提到由于名称冲突(结构体中的字段user.Name和接口上的方法User.Name),接口需要有不同的方法名。这并非必需品,上面的代码已相应地进行了修改:内部结构的字段可以与JSON中的字段名称不同,对应的注释定义了映射关系。


你为什么不建议对@Thomas的答案进行编辑呢?两种解决方案之间的差异非常微妙。 - vasart
微小的差别有时会产生关键性的影响。 - Oleg Sklyar
喜欢这个!有一件事让我有点困扰,就是getter GetName() 应该被称为 Name()。但在这种情况下,你不能这样做,因为你需要一个公共字段在 user 结构体内部,因为标签的缘故。 - Marketa Vlach
1
@MilanVlach 这并不是那么戏剧性的变化,已经改变代码以适应这个要求(见注释)。 - Oleg Sklyar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接