为了账号安全,请及时绑定邮箱和手机立即绑定

尝试使用泛型在 Go 中实现访问者模式

尝试使用泛型在 Go 中实现访问者模式

Go
潇湘沐 2022-12-19 21:10:16
我有以下简单的基于泛型的go包,它实现了 GoF 访客模式:package patternstype 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 的失败。我将关闭这篇文章,但会留下分析,这样其他人就不需要重复了。
查看完整描述

1 回答

?
尚方宝剑之说

TA贡献1788条经验 获得超4个赞

我得出的结论是泛型让这种模式变得更糟。通过参数化Collection结构,您强制items []T拥有相同的元素。使用普通接口,您可以进行动态分派,从而允许items包含不同的实现。仅此一项就应该是充分的理由。


这是一个没有泛型的最小实现,改编自一些 Java 示例(可运行代码):


主要接口:


type Visitor func(Element)


type Element interface {

    Accept(Visitor)

}

实施者:


type Foo struct{}


func (f Foo) Accept(visitor Visitor) {

    visitor(f)

}

容器:


type List struct {

    elements []Element

}


func (l *List) Accept(visitor Visitor) {

    for _, e := range l.elements {

        e.Accept(visitor)

    }

    visitor(l)

}

访客本身,作为常规功能。在这里您可以自由定义任何功能。由于是无类型的,您可以直接将其作为Visitor参数传递:


func doVisitor(v Element) {

    switch v.(type) {

    case *List:

        fmt.Println("visiting list")

    case Foo:

        fmt.Println("visiting foo")

    case Bar:

        fmt.Println("visiting bar")

    }

}


查看完整回答
反对 回复 2022-12-19
  • 1 回答
  • 0 关注
  • 90 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信