亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

嘗試使用泛型在 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 關注
  • 131 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號