【问题标题】:How to implement an interface on a map[int] of type如何在类型的 map[int] 上实现接口
【发布时间】:2019-12-10 06:27:09
【问题描述】:

我正在尝试构建一个可以在 map[int]T 上实现的函数。 我很陌生,想知道这是否可以通过实现接口来完成。

    invoices   map[int]domain.Invoice
    bookings   map[int]domain.Booking
    projects   map[int]domain.Project

所有这些都有以下共同点:

type Invoice struct {
    ID         int
}
type Booking struct {
    ID         int
}
type Project struct {
    ID         int
}

我必须如何通过增加相应类型映射中最后一个项目的 ID 来实现返回所有发票、预订或项目的下一个 ID 的函数?

例如:

func (i *map[int]T) nextID() int {
    return T.ID + 1

【问题讨论】:

  • 这个想法有几个问题:首先,接口只指定方法,而不是字段;其次,即使你有一个可用的接口类型,你也必须专门使用map[int]MyInterface;第三,你只能在你定义的类型上声明方法,你不能直接在map类型上声明方法。
  • 谢谢@Adrian。有没有一种聪明的方法,我可以间接地在地图上声明一个方法?我认为这是一个类似于 C# 中的列表类型,您可以在其中添加和计数项目。 type InvoiceList struct { invoices map[int]domain.Invoice } func (il *InvoiceList) getNextID() int { ... } 这还有一段路要走(没有双关语)?
  • 你可以声明一个类型type MyMapType map[int]domain.Invoice,然后在MyMapType上声明方法。

标签: dictionary go interface


【解决方案1】:

wondered, if this can be done by implementing an interface当然可以。

我不假装提供正确的惯用方式。但是从你说的情况来看,所有这些类型都可以总结为一个IDer接口。

为此,他们必须实现定义为type IDer interface { ID() int } 的接口。这个ID() 方法必须为每个结构类型实现。 使用该通用兼容实现,您可以定义 map[int]IDer 并将其放入 Invoice、Booking、Project 等,只要它兼容即可。

为了防止代码重复,您可以定义一个type IDed struct { ID int}; func (i ID) int {return i.ID},并将其嵌入到您的类型中,例如type Invoice struct { IDed } 等。这样做你仍然可以拨打var x Invoice; x.ID=1; someid := x.ID;

最后,您可以将 map 定义为类型 type mapOfID map[int]IDer 并将方法附加到它 func (m mapOfID) DoSomethigWithIDers(){...}

【讨论】:

    【解决方案2】:

    @mh-cbon 概述了几种方法。地图本身在跟踪“最后一个”项目方面做得很差,因为它不是它们的存储或语义的一部分。解决此问题的一种方法是跟踪最后一个索引或添加到地图中的最后一个 domain 对象以计算下一个 id:

    type IDer interface {
        ID() int
    }
    type IDS struct {
        m    map[int]IDer
        last IDer
    }
    
    func (i *IDS) Add(index int, ider IDer) {
        i.m[index] = ider
        i.last = ider
    }
    
    func (i *IDS) nextID() int {
        if i.last == nil {
          return 1
        }
        return i.last.ID() + 1
    }
    

    上面的示例结合了@mh-cbon 的 Ider 接口以及跟踪最后添加的 Ider 的能力。

    This can then be used 与您的任何domain 对象,只要它们实现IDer 接口:

    type Invoice struct{}
    
    func (i Invoice) ID() int { return 1 }
    
    func main() {
        ids := &IDS{
           m: make(map[int]IDer),
        }
        ids.Add(1, Invoice{})
        fmt.Printf("Next ID: %d\n", ids.nextID())
    
    }
    
    Next ID: 2
    
    Program exited.
    

    【讨论】:

    • 如果存在其他属性,我该如何处理?这些类型只是有共同的 ID,但有更多的属性,而不仅仅是 ID,即 domain.Booking 有 Day int 和 Hours int。我还能访问这些吗?
    • id 生成器函数需要 访问它们吗?与此无关,是的,您可以键入将接口断言为具体类型以访问 tour.golang.org/methods/15 。如果这些项目是计算域对象ID() 所必需的,那么它们可以封装在其ID() 函数中,您无需键入assert。
    • 哇。这些是我还没有发现的深度.. play.golang.org/p/JfCOi1T18Mu
    • 我现在对您的func (i Invoice) ID() int { return 1 } 线路有一个新问题。这始终返回 1,nextID 始终为 2,即使添加更多发票也是如此。
    • 我自己解决了这个问题,并将在我的解决方案中发布答案。
    【解决方案3】:

    根据收集到的所有信息,我得出以下答案:

    https://play.golang.org/p/3RAvclRacbh

    (为了方便命名,我使用了“列表”)

    
    package main
    
    import (
        "fmt"
    )
    
    type Invoice struct{
        Name string
        Id int
    }
    
    // Interface to implement
    type IDer interface {
        ID() int
    }
    // Interface implementation for Invoice
    func (i Invoice) ID() int { return i.Id }
    
    type List struct {
        Items    map[int]IDer
        last IDer
    }
    
    func (i *List) Add(index int, ider IDer) {
        i.Items[index] = ider
        i.last = ider
    }
    
    func (i *List) nextID() int {
            if i.last == nil {
              return 1
            }
        return i.last.ID() + 1
    }
    
    type Repository struct {
        invoices List
    }
    
    func main() {
        r := Repository{}
        r.invoices = List{ 
            Items: make(map[int]IDer), 
        }
    
        i := Invoice{}
        i.Name = "Test"
        i.Id = 1
        r.invoices.Add(1, i)
        ia := r.invoices.Items[1].(Invoice) 
        fmt.Println(ia.Name)
        fmt.Printf("Next ID: %d\n", r.invoices.nextID())
    
    
        i2 := Invoice{}
        i2.Name = "Test2"
        i2.Id = r.invoices.nextID()
        r.invoices.Add(i2.Id, i2)
        ia2 := r.invoices.Items[i2.Id].(Invoice)    
        fmt.Println(ia2.Name)
        fmt.Printf("Next ID: %d\n", r.invoices.nextID())
    
    
        i3 := Invoice{}
        i3.Name = "Test3"
        i3.Id = r.invoices.nextID()
        r.invoices.Add(i3.Id, i3)
        ia3 := r.invoices.Items[i3.Id].(Invoice)    
        fmt.Println(ia3.Name)
        fmt.Printf("Next ID: %d\n", r.invoices.nextID())
    }
    
    
    Test
    Next ID: 2
    Test2
    Next ID: 3
    Test3
    Next ID: 4
    
    Program exited.
    

    感谢@dm03514 提到“类型断言”https://tour.golang.org/methods/15

    【讨论】:

    • 只是让您知道,与切片相同play.golang.org/p/DPQ3cm7lSbJ 我不认为更好地了解您的要求,只是分享。
    • 感谢您对此的更多想法。因为我的计划是使用他们的 ID 作为键将发票添加到地图中,所以这并不是我可以轻松使用的方式。 invoices[invoice.Id] = invoice。也许这种情况,我想实现一个宁静的 api 使我的意图很清楚。 POST /invoices 创建一个新发票 GET /invoices/{invoice.Id} 只需通过使用相关键获取映射的值即可通过 id 获取发票。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-26
    • 2020-11-12
    • 2019-07-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-21
    相关资源
    最近更新 更多