我有以下简单的基于泛型的go包,它实现了 GoF 访客模式:
package patterns
type Social interface {
AcceptVisitor(visitor *Visitor)
}
type Component struct {
}
func (c *Component) AcceptVisitor(visitor *Visitor) {
visitor.VisitComponent(c)
}
type Collection[T Social] struct {
Component
items[]T
}
func (c *Collection[T]) AcceptVisitor(visitor *Visitor) {
visitor.VisitCollection(c) // <- Error Here
}
type Visitor struct {
}
func (v *Visitor) VisitComponent(component *Component) {
}
func (v *Visitor) VisitCollection(collection *Collection[Social]) {
for _, item := range collection.items {
item.AcceptVisitor(v)
}
}
编译器给出以下错误:
./patterns.go:20:26: cannot use c (variable of type *Collection[T]) as
type *Collection[Social] in argument to visitor.VisitCollection
这对我来说似乎很奇怪,因为通用类型 T 被限制为 Social。
我尝试了几件事:
用接口定义替换了访问者抽象类型。这导致了社交和访客界面之间的循环依赖。
从修复问题的声明中删除了泛型,但我们非常需要 Collection 类型的泛型。
看起来go应该能够处理这段代码中的泛型。
可能的解决方案:在与@blackgreen 进行了非常有帮助的讨论之后,我们决定问题出现的原因有以下几点:
Go 是(真正)严格类型化的,不允许将传递给函数的参数“缩小”为原始类型的子集,即使编译器仍然可以证明它是安全的。Go 是否应该允许缩小范围是有争议的。
Go 不允许对方法进行泛型约束,因为约束可能会与与方法关联的结构上的泛型约束发生冲突。
Go,没错,不允许循环依赖。我们可以将访问者模式的所有依赖项抽象为接口,但随后将具有该模式的“双重分派”方面所需的循环依赖项。
为了绕过这些项目,并仍然获得访问者模式的好处,我们可以将访问者结构中的 VisitXYZ() 方法更改为(可能是通用的)函数,每个函数都将 *Visitor 参数作为函数的第一个参数和被访问的对象作为第二个参数。
我在 Go Playground 中发布了这个解决方案:https ://go.dev/play/p/vV7v61teFbj
注意:即使这个可能的解决方案看起来确实可以解决问题,但实际上并没有。如果您考虑编写几种不同类型的访问者(一种用于漂亮打印,一种用于复制,一种用于排序等),您很快就会意识到,由于 VisitXYZ() 函数不是方法,因此您不能为每个函数拥有多个版本每个访客类型。最后,Visitor 模式确实需要 Social 接口和 Visitor 接口之间的循环依赖这一事实注定了 Go 的失败。我将关闭这篇文章,但会留下分析,这样其他人就不需要重复了。
尚方宝剑之说
相关分类