在非本地包中扩展接口方法

Extend an interface method in a non-local package

尝试在 Go 中创建微服务,我有一个包网络来处理获取字节并转换为特定请求:

package network

type Request interface {
}

type RequestA struct {
a int
}

type RequestB struct {
b string
}

func GetRequestFromBytes(conn net.Conn) Request {
buf := make([]byte, 100)
_, _ := conn.Read(buf)
switch buf[0] {
case 0:
    // convert bytes into RequestA
    requestA = RequestAFromBytes(buf[1:])
    return requestA
case 1:
    requestB = RequestBFromBytes(buf[1:])
    return requestB
}}

现在主要是我要处理这个请求。

package main
import (
    "network"
)

(ra *RequestA) Handle() {
// handle here
}

(rb *RequestB) Handle() {
// handle
}

func main() {
    // get conn here
    req = GetRequestFromBytes(conn)
    req.Handle()
}

但是Golang不允许在主包中扩展RequestA/RequestB类。我在网上找到的两个解决方案是 aliasing/embedding 类型,但在这两种解决方案中,我们似乎都必须通过在运行时查找请求的特定类型来处理。这需要在 network 和 main 包中都有一个 switch/if 语句,这会在两个包中重复代码。

最简单的解决方案是将 switch 语句移动到 package main 中,我们在其中确定请求的类型然后处理它,但是 package main 必须处理原始字节,我想避免。查找请求类型并发送它的责任 'upwards' 应由网络包承担。

有没有简单的惯用 Go 方法来解决这个问题?

在主包中使用一个type switch

switch req := network.GetRequestFromBytes(conn).(type) {
case *network.RequestA:
   // handle here, req has type *RequestA in this branch
case *network.RequestB:
   // handle here, req has type *RequestB in this branch
default:
   // handle unknown request type
}

这避免了主包中协议字节的编码知识。

因为问题指定解析代码在网络包中,处理程序代码在主包中,所以在添加新请求类型时无法避免更新两个包中的代码。即使语言支持问题中提出的功能,添加新请求类型时仍然需要更新两个包。

如果您在接口中包装一个 return 值,在这种情况下,您将始终必须对其进行类型切换以根据其类型处理它。

您采用的替代方法是通过向 Request 接口添加一个方法来动态地将处理函数插入到 Request 对象中,该方法允许您注册一个处理程序和另一个方法 Handle() 您可以调用已注册的处理程序来处理每条消息。经验表明,您最终会在每个请求类型定义中得到一堆样板文件。

在 go 中,我可能会采用不同的方法,而不是使用 OO 类型的设计,在这种设计中,每个请求类型都是自己的 "class"。我可能会按照下面的代码行更多地定义网络包。 (警告:这没有经过测试,可能会有一些错别字,还有一些需要填写的错误内容)

type RequestType byte

const (
    ARequest RequestType = iota
    BRequest
    CRequest
    EndRequestRange
)

type HandlerFunc func([]byte) error

type MessageInterface struct {
    handlers map[RequestType]HandlerFunc
    // other stuff
}

func NewMessageInterface() *MessageInterface {
    m := &MessageInterface {
        handlers: make(map[RequestType]HandlerFunc),
    }
    // Do other stuff
    return m
}

func (m *MessageInterface) AddHandler(rt RequestType, h HandlerFunc) error {
    if rt >= EndRequestRange {
        // return an error
    }
    m.handlers[rt]=h

    return nil
}

func (m *MessageInterface) ProcessNextMessage() error {
    buf := make([]byte, 100)
    _, _ := conn.Read(buf)

    h, ok := m.handlers[ReqestType(buff[0])]
    if !ok {
        // probably return error
    }

    return h(buff[1:])  
}

然后在主程序包中初始化 MessageInterface,调用 AddHandler() 以在初始化期间添加所需的每个处理程序,并在主处理期间重复调用 ProcessNextMessage()

这与您所采用的设计方向不同,但对我来说更像是一个 go 方法。

(另请注意 - 我没有做任何事情来确保代码对于并发是安全的,并且这种方法通常用于每个处理程序在其自己的 goroutine 中调用的方式。如果你做到了更改您可能需要在地图周围进行一些互斥保护以及检查其他一些方面)