我正在尝试在 go 中实现一组功能。上下文是一个事件服务器;我想防止(或至少警告)为一个事件多次添加相同的处理程序。
我读过,地图通常用作集合,因为可以轻松检查成员资格:
if _, ok := set[item]; ok {
// don't add item
} else {
// do add item
}
Run Code Online (Sandbox Code Playgroud)
不过,我在使用这种函数范式时遇到了一些麻烦。这是我的第一次尝试:
// 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)
}
Run Code Online (Sandbox Code Playgroud)
很明显为什么这行不通:函数不是 Go 中的引用类型,尽管有些人会告诉你它们是。 我有证据,尽管我们不需要它,因为语言规范规定除了映射、切片和指针之外的所有内容都是按值传递的。
尝试2:
func (ers EventResponseSet) Add(r *EventResponse) {
// ...
}
Run Code Online (Sandbox Code Playgroud)
这有几个问题:
任何 EventResponse 都必须这样声明,fn := func(args interface{}){}因为您无法寻址以通常方式声明的函数。
你根本无法通过闭包。
使用包装器不是一个选项,因为传递给包装器的任何函数都将从包装器获取新地址 - 没有函数可以通过地址唯一标识,并且所有这些仔细的规划都是徒劳的。
我不接受将函数定义为变量作为解决方案是愚蠢的吗?还有另一个(好的)解决方案吗?
需要明确的是,我承认有些情况我无法捕获(关闭),这很好。我设想的用例是定义一堆处理程序,并且相对安全,如果有意义的话,我不会意外地将一个处理程序添加到同一事件两次。
您可以使用reflect.ValueUvelichitel 提供的函数地址,或者string获取的函数地址或获取的fmt.Sprint()地址(更多内容请参阅答案How to Compare 2 function in Go?uintptrreflect.Value.Pointer()),但我建议不要这样做。
由于语言规范不允许比较函数值,也不允许获取它们的地址,因此您无法保证程序中一次有效的东西将始终有效,包括特定的运行,并包括不同的(未来)去编译器吧。我不会使用它。
\n由于规范对此很严格,这意味着编译器可以生成例如在运行时更改函数地址的代码(例如卸载未使用的函数,然后在需要时再次加载)。我目前不知道这样的行为,但这并不意味着未来的 Go 编译器不会利用这样的东西。
\n如果您存储函数地址(以任何格式),则该值不再算作保留函数值。如果没有其他人再“拥有”该函数值,则生成的代码(以及 Go 运行时)将可以“自由”修改/重新定位该函数(从而更改其地址)\xe2\x80\x93,而不会违反规范和 Go 的类型安全。所以你不能理所当然地对编译器感到愤怒和责备,而只能对你自己感到愤怒和责备。
\n如果您想检查是否重用,可以使用接口值。
\n假设您需要带有签名的函数:
\nfunc(p ParamType) RetType\nRun Code Online (Sandbox Code Playgroud)\n创建一个接口:
\ntype EventResponse interface {\n Do(p ParamType) RetType\n}\nRun Code Online (Sandbox Code Playgroud)\n例如,您可能有一个未导出的struct类型,并且指向它的指针可以实现您的EventResponse接口。创建导出函数以返回单个值,因此不会创建新值。
例如:
\ntype myEvtResp struct{}\n\nfunc (m *myEvtResp) Do(p ParamType) RetType {\n // Your logic comes here\n}\n\nvar single = &myEvtResp{}\n\nfunc Get() EventResponse { return single }\nRun Code Online (Sandbox Code Playgroud)\n是否真的需要将实现隐藏在包中,并且只创建和“发布”单个实例?不幸的是,是的,因为否则您可以创建其他值,例如&myEvtResp{}可能不同的指针仍然具有相同的值Do()方法的不同指针,但接口包装器值可能不相等:
\n\n接口值具有可比性。如果两个接口值具有相同的动态类型和相等的动态值,或者两者都有值,则它们相等
\nnil相等。[...和...]
\n指针值具有可比性。如果两个指针值指向同一个变量或者两者的值为 nil,则它们相等。指向不同零大小变量的指针可能相等也可能不相等。
\n
该类型*myEvtResp实现了EventResponse,因此您可以注册它的值(唯一的值,可通过访问Get())。您可以拥有一个类型映射,map[EventResponse]bool您可以在其中存储注册的处理程序、接口值作为键和true值。使用不在映射中的键对映射进行索引会产生映射值类型的零值。因此,如果映射的值类型为bool,则使用不存在的键对其进行索引将导致false\xe2\x80\x93 告诉它不在映射中。使用已注册的EventResponse(现有键)进行索引将导致存储的值 \xe2\x80\x93 true\xe2\x80\x93 告诉它在映射中,它已经注册了。
您可以简单地检查是否已经注册:
\ntype EventResponseSet map[*EventResponse]bool\n\nfunc (ers EventResponseSet) Add(r EventResponse) {\n if ers[r] {\n // warn here\n return\n }\n ers[r] = true\n}\nRun Code Online (Sandbox Code Playgroud)\n结束语:这似乎有点太麻烦了,只是为了避免重复使用。我同意,而且我不会这么做。但如果你想...
\n| 归档时间: |
|
| 查看次数: |
887 次 |
| 最近记录: |