【问题标题】:Function which modifies any structure which has particular fields修改具有特定字段的任何结构的函数
【发布时间】:2017-10-28 02:40:51
【问题描述】:

我有几个结构,它们继承了一些基本结构。像这样的:

type s1 struct {
    a string `json:"a"`
    b string `json:"b"`
}

type s2 struct {
    s1
    c string `json:"c"`
    d string `json:"d"`
}

type s3 struct {
    s1
    c string `json:"c"`
    d string `json:"d"`
    e string `json:"d"`
    f string `json:"d"`
}

现在我需要定义一个函数,它可以在任何具有ab 字段的结构上运行。像

func modifyStruct(s *s1) {
    s.a, s.b = s.b, s.a
}

但必须在 s2、s3 和任何其他继承 s1 的结构上工作。我试图通过一个界面来实现这一点,但到目前为止还没有运气。有什么办法可以做到这一点?模板位于go-playground

【问题讨论】:

  • 为什么不附加一个带有*s1接收器的方法,即func (s *s1) modify() { s.a, s.b = s.b, s.a; }s2s3 变量将转发嵌入的 s1 字段的地址,并将其作为接收者传递给方法;也就是说,s2foo.modify()(&s2foo.s1).modify() 相同。 Playground link
  • 示例中的字段标签表明您计划将 encoding/json 包与这些类型一起使用。如果是这样,那么您应该export 字段名称。 json/encoding 包会忽略未导出的字段。

标签: go struct embedding


【解决方案1】:

一般的解决方案是使用反射,如 Cerise Limón 的回答所示。使用反射的缺点是您必须导出字段,而且它比应该或可能要慢。

在您的示例中,尽管函数采用 *s1 类型的值完全可以且足够,因为所有嵌入 s1 的类型都具有 s1 的值(明确地)。非限定类型名(不带包名)充当嵌入字段的字段名:

s2 := s2{s1: s1{"A", "B"}}
fmt.Println(s2)
modifyStruct(&s2.s1)
fmt.Println(s2)

输出(在Go Playground 上试试):

{{A B}  }
{{B A}  }

如果您仍然希望它接受“任何”(某些)类型的值(因此您不必引用嵌入的 s1 字段),但不想导出字段,那么您可以为此使用接口。使用接口,您可以保留两种解决方案的优点(保持快速、灵活并且您不必导出字段):

type S1 interface {
    AB() (string, string)
    SetAB(a, b string)
}

type s1 struct {
    a string `json:"a"`
    b string `json:"b"`
}

func (s s1) AB() (string, string) { return s.a, s.b }
func (s *s1) SetAB(a, b string)   { s.a, s.b = a, b }

func modifyStruct(s S1) {
    a, b := s.AB()
    s.SetAB(b, a)
}

测试它:

s2 := s2{s1: s1{"A", "B"}}
fmt.Println(s2)
modifyStruct(&s2)
fmt.Println(s2)

输出是一样的(在Go Playground上试试):

{{A B}  }
{{B A}  }

请注意(除了*s1 类型本身)任何自动(隐式)嵌入*s1 的结构类型(以及任何指向结构类型的指针)都实现S1 接口,类似地,任何指向结构类型的指针嵌入s1 还实现了S1(因此在您的示例中为*s2*s3)。

【讨论】:

    【解决方案2】:

    如果你 export 字段,那么你可以使用 reflect 包来交换值:

    type s1 struct {
        A string `json:"a"`
        B string `json:"b"`
    }
    
    ...
    
    func modifyStruct(s interface{}) {
        v := reflect.ValueOf(s).Elem() 
        a := v.FieldByName("A")
        b := v.FieldByName("B")
        t := reflect.New(a.Type()).Elem()
        t.Set(a)
        a.Set(b)
        b.Set(t)
    }
    

    必须导出字段才能使用反射包,因为反射包无法设置未导出的字段。

    playground example

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多