【问题标题】:Extend an interface method in a non-local package在非本地包中扩展接口方法
【发布时间】:2018-10-03 01:31:13
【问题描述】:

尝试在 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 类。我在网上找到的两个解决方案是类型别名/嵌入,但看起来我们都必须通过在运行时查找请求的特定类型来处理。这需要在网络包和主包中都使用 switch/if 语句,这会重复两者中的代码。

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

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

【问题讨论】:

    标签: go interface network-programming microservices


    【解决方案1】:

    在主包中使用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
    }
    

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

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

    【讨论】:

    • 唯一可能的问题是 - 添加新的请求类型需要在两个地方进行更改 - 网络包和主包,所以这里有些重复。
    • 实际上该代码应该使用 switch r := req.(type) - 因为它不是类型开关。
    • @GilesW 它是原始答案和编辑答案中的类型切换。见play.golang.org/p/HSPAzJvk8jR
    • 我会接受他的回答。重复似乎是不可避免的。 Go 语言似乎没有办法避免这种情况。
    • @Metatrix 新增请求类型时需要更新两个包,但没有重复代码。如果您可以按照问题中的尝试扩展非本地包,则在添加新请求类型时仍需要更新两个包。
    【解决方案2】:

    如果您将返回值包装在接口中,则在这种情况下,您将始终需要对其进行类型切换以根据其类型对其进行处理。

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

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

    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()

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-28
      • 1970-01-01
      • 2010-12-01
      相关资源
      最近更新 更多