我的界面
type FirestoreDocument interface {
GetDocumentKey() string
SetDocumentKey(key string)
}
实现它的结构
type ImageProcessingJobDocument struct {
documentKey string
Whatever string `firestore:"whatever"`
}
func (d *ImageProcessingJobDocument) GetDocumentKey() string {
return d.documentKey
}
func (d *ImageProcessingJobDocument) SetDocumentKey(key string) {
d.documentKey = key
}
一些尝试使用上述内容的通用代码。
注意 -
D
实际上是一种返回类型。我不允许将 Execute
与 Execute[D]
通用,大概 Go 不支持这一点,但我可以将 D
粘贴在下面的结构上,并最终在返回元组 (D, error)
中使用它。
type FirestoreUpdate[T any, D *FirestoreDocument] struct {
Collection string
DocumentID string
Data T
}
func NewFirestoreUpdate[T any, D *FirestoreDocument]
(collection, documentID string, data T) *FirestoreUpdate[T, D] {
...
}
func (u *FirestoreUpdate[T, D]) Execute(ctx context.Context, client *firestore.Client) (D, error) {
...
}
这是我的使用方法:
firestoreUpdate := documents.NewFirestoreUpdate[*data.JobProcessingUpdate,
data.ImageProcessingJobDocument](documents.CollectionJobs, key, update)
注意 - 请注意,我在这个泛型方法调用中指定了两种类型,第一个是指针,第二个不是。第二份(工作文件)是我们感兴趣的。
当第二种类型不是指针时(参见上面的用法),我得到:
data.ImageProcessingJobDocument does not satisfy *documents.FirestoreDocument (data.ImageProcessingJobDocument missing in *blah/internal/store/documents.FirestoreDocument)
当我指定第二种类型是指针时,我得到:
*data.ImageProcessingJobDocument does not satisfy *documents.FirestoreDocument (*data.ImageProcessingJobDocument missing in *blah/internal/store/documents.FirestoreDocument)
但是当我不指定类型但允许编译器推断它们时,那么
D
最终会被推断为接口 FirestoreDocument
。
我有 C# 背景,这种事情是可行的。事实上,我可以使
Execute
方法变得通用。
指针是真正令人困惑的,无论是在约束、通用占位符方面(
D
vs. *D
),但在本例中,关于指针接收器以及这可能如何影响接口是由类型还是由类型实现不是,因此为什么指针和直接值似乎都不满足约束/实现接口。
指针修饰符确实将事物与泛型混淆了。
在 Go 中,有一个原则 “接受接口,返回结构”。
这意味着只要您返回
*ImageProcessingJobDocument
,您就应该在 API 中这样做:
func (u *FirestoreUpdate[T]) Execute(ctx context.Context, client *firestore.Client) (*ImageProcessingJobDocument, error) {
return &ImageProcessingJobDocument{}, nil
}
此外,由于您在结构中没有对
FirestoreDocument
执行任何操作,因此它可能不应该在定义中使用:
type FirestoreUpdate[T any] struct {
Collection string
DocumentID string
Data T
}
如果您需要编写“通用方法”,您可以这样做:
func Execute[T any, D FirestoreDocument](ctx context.Context, u *FirestoreUpdate[T], client *firestore.Client) (D, error) {
var d D
return d, errors.New("not implemented")
}
另请参阅 “为什么 Go 不支持带类型参数的方法?”.
一般来说,最好让客户定义他们需要的接口,而不是让它们依赖于你的包。
当您接收像这里这样的接口时,不要使用指针
type FirestoreUpdate[T any, D *FirestoreDocument] struct {
在 Go 中接口是指针,发生的情况是,当您尝试传递 data.ImageProcessingJobDocument
或 *data.ImageProcessingJobDocument
时,两者都会被检查,就好像有一个接口,为了避免这种情况,只需从 FirestoreUpdate
的接收中删除 * 并直接接收 D FirestoreDocument
。
如果不了解更多代码的实际使用情况,就很难判断你的设计是否良好。无论如何,为了解决您的具体问题:
当您写下以下内容时:
func NewFirestoreUpdate[T any, D *FirestoreDocument](/* params */) *FirestoreUpdate[T, D] {
// ...
}
您将
D
的类型约束定义为指向接口的指针。这种限制本质上是没有意义的。接口指针类型负责连接到接口的实现者,例如ImageProcessingJobDocument
。
相反,即使
*ImageProcessingJobDocument
(具体指针类型)满足接口FirestoreDocument
,因为它实现了其方法,它与指向FirestoreDocument
的指针没有关系。
您想要的是将
D
限制为一个简单的界面:
func NewFirestoreUpdate[T any, D FirestoreDocument](/* params */) *FirestoreUpdate[T, D] {
// ...
}
和
type FirestoreUpdate[T any, D FirestoreDocument] struct {
Collection string
DocumentID string
Data T
}
现在
D
可以用实现 FirestoreDocument
的类型实例化。请注意,这样的实现者正是*ImageProcessingJobDocument
,而不是只是ImageProcessingJobDocument
,因为您使用指针接收器声明了方法。
通过定义没有
*
指针的约束,当我开始使用代码时,它似乎允许我选择指向 D 的指针或直接 D。
我之前尝试过这个,但遇到了另一个问题,这导致我尝试指针约束,这导致了SO问题,但我已经解决了另一个问题。
另一个问题只是如何返回默认值
D
,因为目标是在返回元组中为 Execute
定义泛型。
出现错误时,我无法返回
nil, err
,因为 nil 不正确,并且我尝试返回“无”的其他尝试失败了,在 C# 中我会使用 default(D)
。
无论如何,语法是这样的:
// remove the * to make the constraints all [T any, D FirestoreDocument]
// then return nothing like this;
func (u *FirestoreUpdate[T, D]) Execute(ctx context.Context, client *firestore.Client) (D, error) {
var doc D
err := doSomething()
if (err) {
return doc, fmt.Errorf("whoops: %w", err)
}
...
}