当前位置: 首页 > 面试题库 >

Go中独特功能的集合

黄鸣
2023-03-14
问题内容

我正在尝试实现一组功能。上下文是事件服务器;我想防止(或至少警告)为一个事件多次添加同一处理程序。

我已经读过,由于易于检查成员资格,因此映射是惯用的集:

if _, ok := set[item]; ok {
    // don't add item
} else {
    // do add item
}

我在使用此范式进行功能时遇到了一些麻烦。这是我的第一次尝试:

// this is not the actual signature
type EventResponse func(args interface{})

type EventResponseSet map[*EventResponse]struct{}

func (ers EventResponseSet) Add(r EventResponse) {
    if _, ok := ers[&r]; ok {
        // warn here
        return
    }
    ers[&r] = struct{}{}
}

func (ers EventResponseSet) Remove(r EventResponse) {
    // if key is not there, doesn't matter
    delete(ers, &r)
}

很显然,这是行不通的:在Go中,函数不是引用类型,尽管有些人会告诉你它们是。
我有证明,尽管我们不需要它,因为语言规范指出除映射,切片和指针以外的所有内容都是按值传递的。

尝试2:

func (ers EventResponseSet) Add(r *EventResponse) {
// ...
}

这有两个问题:

  • 任何EventResponse都必须像这样声明,fn := func(args interface{}){}因为您无法处理以通常方式声明的函数。

  • 您根本无法通过闭包。

  • 使用包装器不是一种选择,因为传递给包装器的任何函数都将从包装器获取新地址-没有任何函数可以通过地址唯一标识,并且所有这些仔细的计划都是不可行的。

不接受将函数定义为变量作为解决方案对我来说很愚蠢吗?还有其他(好的)解决方案吗?

明确地说,我接受某些情况下我无法捕获(关闭),这很好。我设想的用例是定义一堆处理程序,并且相对安全,这样我就不会意外地将一个事件添加两次到同一事件中。


问题答案:

您可以使用reflect.ValueUvelichitel提出的功能,也可以使用功能地址作为的string获取者,也可以使用fmt.Sprint()地址作为的uintptr获取者reflect.Value.Pointer(),但是我建议您反对使用它。

由于语言规范不允许比较函数值,也不允许采用它们的地址,因此您不能保证程序中一次起作用的东西总是可以起作用,包括特定的运行,并且包括不同的(未来)去编译器。我不会用它。

由于规范对此非常严格,因此这意味着允许编译器生成代码,例如,它们将在运行时更改函数的地址(例如,卸载未使用的函数,然后在以后再次需要时再次加载)。我目前不知道这种行为,但这并不意味着将来的Go编译器将不会利用这种行为。

如果您以任何格式存储函数地址,则该值不再视为保留函数值。如果没有其他人会“拥有”函数值,那么生成的代码(和Go运行时)将是“自由的”,可以修改/重新定位函数(从而更改其地址),而不会违反规范和Go的类型安全性。因此,您不能理会并责怪编译器,而只能怪您自己。

如果要检查重用,则可以使用接口值。

假设您需要带有签名的函数:

func(p ParamType) RetType

创建一个接口:

type EventResponse interface {
    Do(p ParamType) RetType
}

例如,您可以具有未导出的struct类型,并且指向它的指针可以实现您的EventResponse接口。使导出函数返回单个值,因此不能创建新值。

例如:

type myEvtResp struct{}

func (m *myEvtResp) Do(p ParamType) RetType {
    // Your logic comes here
}

var single = &myEvtResp{}

func Get() EventResponse { return single }

是否真的需要将实现隐藏在包中,并且仅创建和“发布”单个实例?不幸的是,是的,因为否则您可以创建其他值,例如&myEvtResp{}可能是具有相同Do()方法的不同指针,但接口包装器值可能不相等:

接口值是可比较的。如果两个接口值具有相同的动态类型和相等的动态值,或者两个接口值都具有value,则它们是相等的nil

[…和…]

指针值是可比较的。如果两个指针值指向同一个变量,或者两个指针值均为nil,则它们相等。指向不同零大小变量的指针可以相等或可以不相等。

该类型*myEvtResp实现EventResponse,因此您可以注册它的值(唯一的值,可通过访问Get())。您可以具有一个类型映射,可以map[EventResponse]bool在其中存储注册的处理程序,接口值作为键和true值存储。使用不在地图中的键为地图编制索引会产生地图的值类型的零值。因此,如果地图的值类型为bool,则使用不存在的键对其进行索引将导致false–告诉它不在地图中。用已经注册EventResponse的键(现有键)建立索引将得到存储的值true–告诉它在地图中,它已经被注册。

您可以简单地检查一个是否已经注册:

type EventResponseSet map[*EventResponse]bool

func (ers EventResponseSet) Add(r EventResponse) {
    if ers[r] {
        // warn here
        return
    }
    ers[r] = true
}

结束语: 为避免重复使用,这似乎有点麻烦。我同意,我不会去做。但是如果你想…



 类似资料:
  • 我们经常需要程序去处理一些集合数据,比如选出所有符合条件的数据或者使用一个自定义函数将一个集合元素拷贝到另外一个集合。 在一些语言里面,通常是使用泛化数据结构或者算法。但是Go不支持泛化类型,在Go里面如果你的程序或者数据类型需要操作集合,那么通常是为集合提供一些操作函数。 这里演示了一些操作strings切片的集合函数,你可以使用这些例子来构建你自己的函数。注意在有些情况下,使用内联集合操作代码

  • 我想用Criterias执行这个查询,但它似乎没有按预期工作: 我试过这个 但是它返回一个结果,就像我在执行这个查询一样 它只返回列描述。 我的问题是,如何告诉Hibernate我想要表中的所有列,但我不想重复Description列中的变量?

  • 问题内容: 情况: 我想从控制台获取密码输入- 而不回显用户键入的内容 。有什么可以和Go中的功能媲美吗? 我试过的 我尝试使用,但它回显了键入的内容。 问题答案: 您可以通过执行以下操作来关闭回显,然后在读取密码后将其重新打开

  • 问题内容: 在此站点上为另一个答案编写代码时,我遇到了这种特殊性: 首先,我很困惑为什么对编译器的调用正常。当未提及任何未经检查的异常类型时,它推断出什么可能的类型? 其次,接受这一工作原理后,为什么编译器会在调用中抱怨?他们看起来非常相似。 问题答案: 的T 推断为。可以从有关类型推断的语言规范(http://docs.oracle.com/javase/specs/jls/se8/html/j

  • 问题内容: 我正在通过编写一个小型个人项目来学习Go。即使很小,我还是决定从头开始进行严格的单元测试,以学习Go的良好习惯。 琐碎的单元测试都很好而且花哨的,但是我现在对依赖项感到困惑;我希望能够用模拟函数替换一些函数调用。这是我的代码片段: 我希望能够测试downloader()而不实际通过http获取页面- 即通过模拟get_page(更容易使用,因为它仅将页面内容作为字符串返回)或http.

  • 问题内容: 装饰器模式(功能)有很多好处: 当一个方法具有许多正交的关注点时,这将非常有用…也就是说,这些关注点均不相关,除了我们每次调用我们的方法时都希望全部(或部分)关注它们。这是装饰器模式真正有用的地方。 通过实现装饰器模式,我们订阅了开闭主体。我们的方法对将来的扩展开放,但对将来的修改不开放。遵循开放-封闭原则有很多有趣的好处。 但是,我发现的所有示例都非常复杂(例如,编写带有许多中间件的