重新认识Go的interface

目录

 

1. 对interface的基本认识

2. interface也是一种类型

3. interface类型的序列化问题

4. 结论


1. 对interface的基本认识

遇到如下需求:将JSON,XML,YAML等格式的文本相互转换。

interface是Go有别于传统OOP编程的重要设计,这种非侵入式的接口设计,让接口实现和接口调用完美解耦。

对interface的第一印象如下:

  1. 非侵入式接口调用规范
  2. 定义一组函数调用规范,只要实现了约定的函数签名,即可实现接口赋值
  3. 是实现类似OOP中多态的重要工具
  4. 由于不需要显式声明继承关系,所以Go不再需要OOP的类型继承关系图

2. interface也是一种类型

从reflect的类型Kind定义可以看到,reflect.Interface也是一种数据类型。

也就是说Interface与int其实是可以同化处理的。

然而,interface与普通类型有所区别:

我们可以用已有重定义任何类型并为新类型增加method,然而重命名interface,却不能为其增加method。


type MyInt int
// It's ok to define method for normal type
func (m MyInt) Show() {
	println(m)
}

type MyInterface interface{}
// invalid receiver type MyInterface (MyInterface is an interface type)
func (m MyInterface) Show() {
	fmt.Println(m)
}

因此如果我们想通过如下方法来改变某个interface的JSON序列化方法是不可能实现的:

type MyInterface interface{}
// invalid receiver type *MyInterface (MyInterface is an interface type)

func (m *MyInterface) UnmarshalJSON(data []byte) error {
	*m = append([]byte(nil), data...)
	return nil
}

 

3. interface类型的序列化问题

我们时常会有如下需求:将JSON,XML,YAML等格式的文本相互转换。

与一般序列化需求已知输入文本的格式,只需要定义满足格式的struct等类型来unmarshal的需求不一样,

这里根本不关心具体的输入文本是什么格式,只需要有一个通用的Go数据结构,能够接收任意输入,并marshal为另一种格式的输出即可。

看起来interface{}正是我们想要的通用数据结构。

我们可以轻易实现如下通用序列化方法:

func TestUniversalUnmarshal(t *testing.T) {
	var d struct {
		D interface{}
	}
	var txt = `
{
	"D":{
	  "a": 1,
	  "b": "foo"
	}
}
`
	json.Unmarshal([]byte(txt), &d)
	fmt.Printf("unmarshal txt: %s\nGo data:%#v\n", txt, d)
	// output:
	// unmarshal txt:
	// {
	// "D":{
	//   "a": 1,
	//   "b": "foo"
	// }
	// }
	// Go data:struct { D interface {} }{D:map[string]interface {}{"a":1, "b":"foo"}}
}

可以看到,通用数据结构D(interface{})完美的接收了任意json的输入,并转换为合法的Go通用数据结构map[string]interface{}。

因此,我们很容易想到通用的json unmarshal方法:

func FromJson(s string) (interface{}, error) {
	var d struct {
		D interface{}
	}
	ss := fmt.Sprintf(`{"D":%s}`, s)
	err := json.Unmarshal([]byte(ss), &d)
	return d.D, err
}

燃鹅,反序列化YAML让我们犯难了。我们不可能单纯的加2个大括号构造出一个合法的YAML字符串,因为YAML用空格缩进表示层级关系,意味着我们需要给每一行增加一个空格,才能构造出一个合法的YAML字符串。

反思以上方法,我们通过未初始化的interface{} D实现了接收任意类型的json输入,是否意味着我们可以用nil来调用json.Unmarshal达到相同的效果呢?

func TestUniversalUnmarshal2(t *testing.T) {
	var d interface{}
	var txt = `
{
  "a": 1,
  "b": "foo"
}
`
	err := json.Unmarshal([]byte(txt), d)
	if err != nil {
		t.Fatal(err)
	}
	fmt.Printf("unmarshal txt: %s\nGo data:%#v, err=%v\n", txt, d, err)
	// output:
	// json: Unmarshal(nil)
}

可见,json.Unmarshal(data []byte, v interface{}) error不接受未初始化的interface{}(nil)作为输入参数。

通过简单的reflect测试,可以发现interface{}底层的秘密:

func TestInterfaceReflect(t *testing.T) {
	var d interface{}
	Unmarshal(nil, d)
	Unmarshal(nil, &d)
	// output:
	// Unmarshal v=<nil> rt=<nil> rv=<invalid reflect.Value>
	// Unmarshal v=(*interface {})(0xc00003a740) rt=*interface {} rv=0xc00003a740
	//  rt=ptr rv=0xc00003a740
	//    rtreal=interface rv=0xc00003a740
}

func Unmarshal(data []byte, v interface{}) error {
	rv := reflect.ValueOf(v)
	rt := reflect.TypeOf(v)

	fmt.Printf("Unmarshal v=%#v rt=%v rv=%v\n", v, rt, rv)
	if rt != nil {
		fmt.Printf("  rt=%v rv=%v\n", rt.Kind(), rv)
		if rt.Kind() == reflect.Ptr {
			rtReal := rt.Elem()
			fmt.Printf("    rtreal=%v rv=%v\n", rtReal.Kind(), rv)
		}
	}
	return nil
}

通过interface{} -> interface{} 得到的是 nil type invalid value nil。

通过*interface{} -> interface{} 得到的是 ptr type -> interface{}。

因此,我们可以得知,interface{} 还可以接收 *interface 作为输入,得到的是ptr->interface的指针。这正是我们想要的通过interface{}接收任意文本输入作为通用Go数据结构的方法。

func FromJson(s string) (interface{}, error) {
	var d interface{}
	err := json.Unmarshal([]byte(s), &d) // decode to *interface{}
	return d, err
}
func FromYaml(s string) (interface{}, error) {
	var d interface{}
	err := yaml.Unmarshal([]byte(s), &d) // decode to *interface{}
	return d, err
}

踏破铁鞋无觅处,得来全不费工夫。这正是我们需要的接收任意合法字符串输入,不需要对输入数据做任何处理,即可得到与其等价的通用Go数据结构interface{}的方法。 

基于此方法,我实现了通用的JSON,YAML,XML相互转换的库,在这里

4. 结论

  • Go interface是一种特殊的Go类型,不允许为该类型增加method
  • interface{} 可以赋值为普通类型的Value,得到的数据是只读的
  • interface{} 可赋值为普通类型的指针,得到的是reflect.Ptr类型,其Value是assignable的
  • interface{} 接收为赋值的 interface{}作为输入,得到的还是未赋值的interface{}<nil>
  • interface{} 可赋值为 *interface,得到的是reflect.Ptr类型,只值为interface类型,可用于反序列化为任意的Go数据结构
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值