首页
Preview

Golang泛型与接口:最佳实践

简介

自从Go 1.18版本开始,Go开始支持泛型。然而,本文并不是关于什么是泛型(已经有很多文章了),而是关于何时使用它们的问题。显而易见的TLDR是:当你需要为多个类型编写相同的逻辑时,使用泛型。这听起来很容易理解,但当我们面对实际情况时,我们还有另一个选择:使用接口。那么问题来了:何时使用泛型而不是接口?我们将在本文中使用示例来讨论这个问题。

从一个简单的例子开始

sort.Sort提供了一种对Interface类型的切片进行排序的方法。

func Sort(data Interface)

Interface被定义为:

type Interface interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

因此,如果一个arrayLenLessSwap方法,它就可以通过sort.Sort进行排序。

在许多情况下,泛型可以实现相同的目标。而且在大多数情况下,它们具有类似的性能。但是,泛型有一个真正的优势:**你可以直接知道具体类型。**这为什么很有用呢?

为什么我们需要知道具体类型?

让我们看另一个例子。假设你需要编写一个存储控制器,可以从给定的文件中读取/写入数据。你只需要对象能够将其序列化/反序列化为/自JSON。你可以像这样编写代码:

type Object interface {
  ...
}
type FileStorage struct {
  fileName string
}
func (s *FileStorage) Load(ctx context.Context, ch chan<- Object) error {
  ...
}
func (s *FileStorage) Save(ctx context.Context, ch <-chan Object) error {
  ...
}

对于Save,没有问题。由于Object是一个interface,因此可以传递任何实现Object接口的对象。实际类型在Marshal的运行时已知。然而,对于Load,问题是在Unmarshall之前你不知道对象的实际类型。解决这个问题的一种冗长的方法是在你MarshalUnmarshall它时使用一个辅助结构来包装对象和类型信息。这很烦人。

但是让我们想一想,为什么我们需要存储这些信息呢?答案是:**我们不知道对象的具体类型,因为它是一个接口。使用泛型,你可以以一种更加优雅的方式解决这个问题:

type FileStorage[OBJ any] struct {
  fileName string
}
func (s *FileStorage[OBJ]) Load(ctx context.Context, ch chan<- OBJ) error {
  orig, err := os.ReadFile(s.fileName)
  if err != nil {
     return err
  }
  var data []OBJ
  if err = json.Unmarshal(orig, &data); err != nil {
     return err
  }
  for _, obj := range data {
     select {
     case <-ctx.Done():
        return ctx.Err()
     case ch <- obj:
     }
  }
  return nil
}

因为OBJ是一个具体类型,所以你可以直接在Load函数中使用它。你不需要使用包装器结构来存储类型信息。你可能唯一遇到的问题是需要使用具体类型来实例化FileStorage。但是在大多数情况下,这应该是可行的,因为你的程序中只有几种类型。

结论

在本文中,我们讨论了何时使用泛型而不是接口。一个更具体的最佳实践是:

  • 对于控制器对象,使用接口使它们更加灵活。
  • 对于数据对象,使用泛型使你的代码更加简洁和优雅。

版权声明:本文内容由TeHub注册用户自发贡献,版权归原作者所有,TeHub社区不拥有其著作权,亦不承担相应法律责任。 如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

点赞(0)
收藏(0)
菜鸟一只
你就是个黄焖鸡,又黄又闷又垃圾。

评论(0)

添加评论