我有一个看起来有点像的函数
func (c *Context) RegisterHandler(f interface{}) error {
// Do lots of checking to make sure a function was passed in
// and it's arguments all implement a specific interface
}
func consumer(a ConcreteTypeA, b ConcreteTypeB) {}
func main() {
...
c.RegisterHandler(consumer)
}
大致可以归结为
var a ConcreteTypeA
var b ConcreteTypeB
a.InitFromContext(c)
b.InitFromContext(c)
consumer(a, b)
有没有办法让
interface{}
更具体而不改变其行为?如果不是,我对我的解决方案相当满意。我只是希望我可以减少运行时检查和编译时可能未被捕获的错误。
我当前的实现看起来与上面非常相似。我尝试过做类似的事情
func (c *Context) RegisterHandler(f func (...InitFromContexter)) error {
// Do lots of checking to make sure a function was passed in
// and it's arguments all implement a specific interface
}
但这会导致所有现有代码失败,因为它只会接收可变参数函数
你已经用你的
RegisterHandler(f interface{})
找到了路,真的没有其他的了。
事实是,如果您想支持多个签名,您还需要有代码来处理您支持的所有各种签名模式,以便最终可以实际进行调用。
正如您已经写的,这将是一个运行时检查的意大利面条球,实际上也是基于反射的检查和调用。如果在您的项目中可行的话,拥有类似
RegisterHandler(handler interface{ Do() }
的内容可能会更清晰、更容易理解(尤其是在以后),其中您的各种不同模式显示为特定于实现的结构字段。
我的第一个问题是:为什么将处理程序作为具有未知签名的函数?
你所做的与定义没有什么不同
func (ctx *Context) RegisterHandler(handler func(*Context)) {
ctx.handler = handler
}
然后就打电话
func main() {
ctx := &Context{name: "test"}
ctx.RegisterHandler(func(context *Context) {
ConcreteTypeA{}.InitFromContext(context)
fmt.Println("OneArgHandler ConcreteTypeA")
})
ctx.handler(ctx)
ctx.RegisterHandler(func(context *Context) {
ConcreteTypeB{}.InitFromContext(context)
fmt.Println("OneArgHandler ConcreteTypeB")
})
ctx.handler(ctx)
ctx.RegisterHandler(func(context *Context) {
ConcreteTypeA{}.InitFromContext(context)
ConcreteTypeB{}.InitFromContext(context)
fmt.Println("TwoArgHandler ConcreteTypeA ConcreteTypeB")
})
ctx.handler(ctx)
ctx.RegisterHandler(func(context *Context) {
ConcreteTypeA{}.InitFromContext(context)
ConcreteTypeB{}.InitFromContext(context)
ConcreteTypeA{}.InitFromContext(context)
fmt.Println("ThreeArgHandler ConcreteTypeA ConcreteTypeB ConcreteTypeA")
})
ctx.handler(ctx)
}
只是后者更具可读性。问题是,在您的示例中,您只能传递您的
*Context
,并且调用函数(上下文)需要知道要传递哪些参数,这使得选择显然受到限制。
所以,我建议改用闭包。
我想出了一个接近的解决方案。它很丑陋,但似乎按照我期望的方式工作。它确实满足了无需编辑原始处理程序代码的要求。所有注册代码都在一个地方,因此这应该比更改所有功能本身更容易处理。
我并不完全满意,但我认为这是一个比我在运行时处理所有这些问题之前更好的解决方案。不过,它确实使注册代码变得更加嘈杂。希望能找到更好的中间立场。
package main
import "fmt"
type Handler interface {
GenerateHandler() func(*Context)
}
type Context struct {
name string
handler func(*Context)
}
func (ctx *Context) RegisterHandler(handler Handler) {
ctx.handler = handler.GenerateHandler()
}
type InitFromContext interface {
InitFromContext(ctx *Context)
}
type ConcreteTypeA struct{}
type ConcreteTypeB struct{}
func (c ConcreteTypeA) InitFromContext(ctx *Context) {
fmt.Println("ConcreteTypeA InitFromContext", ctx.name)
}
func (c ConcreteTypeB) InitFromContext(ctx *Context) {
fmt.Println("ConcreteTypeB InitFromContext", ctx.name)
}
type OneArgHandler[T InitFromContext] func(a T)
// These new functions are so we can get type errasure.
// Go doesn't support this for types only for functions
func NewOneArgHandler[T InitFromContext](
f func(a T),
) OneArgHandler[T] {
return OneArgHandler[T](f)
}
func (f OneArgHandler[T]) GenerateHandler() func(*Context) {
return func(ctx *Context) {
var a T
a.InitFromContext(ctx)
f(a)
}
}
type TwoArgHandler[T, T2 InitFromContext] func(a T, b T2)
func NewTwoArgHandler[T, T2 InitFromContext](
f func(a T, b T2),
) TwoArgHandler[T, T2] {
return TwoArgHandler[T, T2](f)
}
func (f TwoArgHandler[T, T2]) GenerateHandler() func(*Context) {
return func(ctx *Context) {
var a T
a.InitFromContext(ctx)
var b T2
b.InitFromContext(ctx)
f(a, b)
}
}
type ThreeArgHandler[T, T2, T3 InitFromContext] func(a T, b T2, c T3)
func NewThreeArgHandler[T, T2, T3 InitFromContext](
f func(a T, b T2, c T3),
) ThreeArgHandler[T, T2, T3] {
return ThreeArgHandler[T, T2, T3](f)
}
func (f ThreeArgHandler[T, T2, T3]) GenerateHandler() func(*Context) {
return func(ctx *Context) {
var a T
a.InitFromContext(ctx)
var b T2
b.InitFromContext(ctx)
var c T3
c.InitFromContext(ctx)
f(a, b, c)
}
}
func main() {
ctx := &Context{name: "test"}
ctx.RegisterHandler(NewOneArgHandler(func(a ConcreteTypeA) {
fmt.Println("OneArgHandler ConcreteTypeA")
}))
ctx.handler(ctx)
ctx.RegisterHandler(NewOneArgHandler(func(a ConcreteTypeB) {
fmt.Println("OneArgHandler ConcreteTypeB")
}))
ctx.handler(ctx)
ctx.RegisterHandler(
NewTwoArgHandler(func(a ConcreteTypeA, b ConcreteTypeB) {
fmt.Println("TwoArgHandler ConcreteTypeA ConcreteTypeB")
}),
)
ctx.handler(ctx)
ctx.RegisterHandler(
NewThreeArgHandler(
func(a ConcreteTypeA, b ConcreteTypeB, c ConcreteTypeA) {
fmt.Println("ThreeArgHandler ConcreteTypeA ConcreteTypeB ConcreteTypeA")
},
),
)
ctx.handler(ctx)
}