Go - 巧用Constraint接口实现约束

在Go开发中,通常会使用类似 var _ core.TweetSearchService = (*bridgeTweetSearchServant)(nil) 的语句借助Go编译器在 编译期 检测某个类型是否完全实现了某个接口的所有方法,比如此例, 如果 *bridgeTweetSearchServant 缺失 接口 core.TweetSearchService 的某些方法实现,Go将会在编译期报错,不能成功编译App.

在工程实践中,可以在 包内 将这类 接口实现约束(Constraint) 语句 统一放在 constraint.go 中,并添加 build tag //go:build constraint 使用tag条件编译机制,只有在使用 go build -tags constraint 时才会进行接口实现约束的检测,在编译产品App时就不带 constraint tag,也就不会把这些 只应在开发期有效的语句 编译到最终的App中。

以下是 『paopao-ce』 的一个使用实例:

// file: https://github.com/rocboss/paopao-ce/blob/dev/internal/dao/search/constraint.go

//go:build constraint

package search

import "github.com/rocboss/paopao-ce/internal/core"

var (
	_ core.TweetSearchService = (*bridgeTweetSearchServant)(nil)

	_ core.TweetSearchService = (*meiliTweetSearchServant)(nil)
	_ core.VersionInfo        = (*meiliTweetSearchServant)(nil)

	_ core.TweetSearchService = (*zincTweetSearchServant)(nil)
	_ core.VersionInfo        = (*zincTweetSearchServant)(nil)
)
  • 使用 //go:build constraint 标记 只在 有 -tags constraint 时才进行 接口实现约束 的检测;
  • 形如 var _ core.TweetSearchService = (*bridgeTweetSearchServant)(nil) 的语句用于保证某个类型实现了接口定义的所有方法,否则将在编译期间报错;
  • 由于 接口实现约束语句 只会在 条件编译期间 有效,因此可以不用考虑内存分配而导致的副作用,也就可以使用类似 var _ core.TweetSearchService = new(bridgeTweetSearchServant) 这样的语句,更简洁的编写约束语句;
// file: https://github.com/rocboss/paopao-ce/blob/dev/internal/core/search.go

// TweetSearchService tweet search service interface
type TweetSearchService interface {
	IndexName() string
	AddDocuments(data []TsDocItem, primaryKey ...string) (bool, error)
	DeleteDocuments(identifiers []string) error
	Search(user *ms.User, q *QueryReq, offset, limit int) (*QueryResp, error)
}
  • 此例中的接口定义;
// file: https://github.com/rocboss/paopao-ce/blob/dev/internal/dao/bridge.go

type bridgeTweetSearchServant struct {
	ts               core.TweetSearchService
	updateDocsCh     chan *documents
	updateDocsTempCh chan *documents
}

func (s *bridgeTweetSearchServant) IndexName() string {
	return s.ts.IndexName()
}

func (s *bridgeTweetSearchServant) AddDocuments(data []core.TsDocItem, primaryKey ...string) (bool, error) {
	s.updateDocs(&documents{
		primaryKey: primaryKey,
		docItems:   data,
	})
	return true, nil
}

func (s *bridgeTweetSearchServant) DeleteDocuments(identifiers []string) error {
	s.updateDocs(&documents{
		identifiers: identifiers,
	})
	return nil
}

func (s *bridgeTweetSearchServant) Search(user *ms.User, q *core.QueryReq, offset, limit int) (*core.QueryResp, error) {
	return s.ts.Search(user, q, offset, limit)
}

// ...other logic...
  • 此例中的接口实现类型;

  • vs code 编辑器的设置中,Go:Build Tags 添加 constraint tag,编辑器才会将 //go:build constraint 标记的go文件添加在项目中高亮显示,同时会实时做 接口实现约束 的检测工作,建议在开发环境中正确设置,方便有效的进行项目开发。
// file: https://github.com/rocboss/paopao-ce/blob/v0.6.0-alpha.3/internal/dao/search/bridge.go

// 接口实现约束,go在 编译期 检测 `*bridgeTweetSearchServant` 实现了接口 `core.TweetSearchService`
// 如果 检测到缺失某些 接口方法 的实现,将不能成功编译App.
var _ core.TweetSearchService = (*bridgeTweetSearchServant)(nil)

type bridgeTweetSearchServant struct {
	ts               core.TweetSearchService
	updateDocsCh     chan *documents
	updateDocsTempCh chan *documents
}

func (s *bridgeTweetSearchServant) IndexName() string {
	return s.ts.IndexName()
}

func (s *bridgeTweetSearchServant) AddDocuments(data []core.TsDocItem, primaryKey ...string) (bool, error) {
	s.updateDocs(&documents{
		primaryKey: primaryKey,
		docItems:   data,
	})
	return true, nil
}

func (s *bridgeTweetSearchServant) DeleteDocuments(identifiers []string) error {
	s.updateDocs(&documents{
		identifiers: identifiers,
	})
	return nil
}

func (s *bridgeTweetSearchServant) Search(user *ms.User, q *core.QueryReq, offset, limit int) (*core.QueryResp, error) {
	return s.ts.Search(user, q, offset, limit)
}

// ...other logic...
  • 项目中也可以 直接 在邻近接口实现的位置添加 接口实现约束 语句,如上面的 var _ core.TweetSearchService = (*bridgeTweetSearchServant)(nil),这种方式对App的运行其实没有什么实质影响,可以放心使用。
 Building a message queue with only two UNIX signals