处理不同类型的Golang处理程序

处理不同类型的Golang处理程序,go,dry,Go,Dry,这些是我在研究gorilla/mux时在网上发现的模式中的应用处理程序。它们是满足http.Handler的结构的一部分。如果您注意到,以下两个块完全相同。实际上,它们可以作为字符串传递“variant”(“flow”或“process”) func CreateFlow(a *AppContext, w http.ResponseWriter, r *http.Request) (int, error) { highest, code, err := a.Create("flow",

这些是我在研究gorilla/mux时在网上发现的模式中的应用处理程序。它们是满足http.Handler的结构的一部分。如果您注意到,以下两个块完全相同。实际上,它们可以作为字符串传递“variant”(“flow”或“process”)

func CreateFlow(a *AppContext, w http.ResponseWriter, r *http.Request) (int, error) {

    highest, code, err := a.Create("flow", r)
    if code != 200 || err != nil {
        return code, err
    }

    b := new(bytes.Buffer)
    json.NewEncoder(b).Encode(struct {
        Highest int `json:"id"`
    }{highest})
    w.Header().Set("Content-Type", "application/json")
    w.Write(b.Bytes())
    return 200, nil
}

func CreateProcess(a *AppContext, w http.ResponseWriter, r *http.Request) (int, error) {

    highest, code, err := a.Create("process", r)
    if code != 200 || err != nil {
        return code, err
    }

    b := new(bytes.Buffer)
    json.NewEncoder(b).Encode(struct {
        Highest int `json:"id"`
    }{highest})
    w.Header().Set("Content-Type", "application/json")
    w.Write(b.Bytes())
    return 200, nil
}
但是,以下两个块不仅需要字符串,还需要关联类型的变量(“Flow”和“Process”)来成功地解组我从ElasticSearch获得的命中。除此之外,它们是相同的代码

func GetFlow(a *AppContext, w http.ResponseWriter, r *http.Request) (int, error) {

    hit, code, err := a.GetByID("flow", mux.Vars(r)["id"], r)
    if code != 200 {
        return code, err
    }

    var flow Flow

    err = json.Unmarshal(*hit.Source, &flow)
    if err != nil {
        return 500, err
    }

    flow.ESID = hit.Id

    b := new(bytes.Buffer)
    json.NewEncoder(b).Encode(flow)
    w.Header().Set("Content-Type", "application/json")
    w.Write(b.Bytes())
    return 200, nil
}

func GetProcess(a *AppContext, w http.ResponseWriter, r *http.Request) (int, error) {

    hit, code, err := a.GetByID("process", mux.Vars(r)["id"], r)
    if code != 200 {
        return code, err
    }

    var process Process

    err = json.Unmarshal(*hit.Source, &process)
    if err != nil {
        return 500, err
    }

    process.ESID = hit.Id

    b := new(bytes.Buffer)
    json.NewEncoder(b).Encode(process)
    w.Header().Set("Content-Type", "application/json")
    w.Write(b.Bytes())
    return 200, nil
}
我不知道当涉及到一个声明的类型时,如何在golang中推广这种行为。这些处理程序也都在同一个包中,因为我认为它们都在完成类似的任务。我很清楚地在代码中重复我自己,但我需要关于如何改进的建议。我已经走过了“一点复制比一点依赖要好”的历程,但我害怕,因为“反射永远不清晰”

下面是一个主要使用这些函数之一的声明示例

api.Handle("/flow/{id:[0-9]+}", handlers.AppHandler{context, handlers.GetFlow}).Methods("GET")

您可以通过传入必要类型的示例来完成此操作,方法与
Unmarshal
完成此操作的方法相同:

func GetFlow(a *AppContext, w http.ResponseWriter, r *http.Request) (int, error) {
    return GetThing(a,w,r,"flow",new(Flow))
}

func GetProcess(a *AppContext, w http.ResponseWriter, r *http.Request) (int, error) {
    return GetThing(a,w,r,"process",new(Process))
}

func GetThing(a *AppContext, w http.ResponseWriter, r *http.Request, t string, ob Elastible{}) (int, error) {
    hit, code, err := a.GetByID(t, mux.Vars(r)["id"], r)
    if code != 200 {
        return code, err
    }

    err = json.Unmarshal(*hit.Source, ob)
    if err != nil {
        return 500, err
    }

    ob.SetESID(hit.Id)

    b := new(bytes.Buffer)
    json.NewEncoder(b).Encode(ob)
    w.Header().Set("Content-Type", "application/json")
    w.Write(b.Bytes())
    return 200, nil
}

type Elastible interface {
    SetESID(id ESIDType)    // whatever type ESID is, not clear from example
}

func (f *Flow) SetESID(id ESIDType) {
    f.ESID = id
}

这段代码未经测试(因为我没有struct def或其他依赖代码),但我希望它能让人明白这一点。

好的,我提出了一个解决方案,它将为您提供最大的代码重用和最小的代码复制。在我看来,这是迄今为止最通用的解决方案。我们还将考虑由给出的答案来完成解决方案。您只需定义一个高阶函数
CreateHandler
,该函数将返回具有以下签名的函数:
func(*AppContext,http.ResponseWriter,http.Request)(int,error)

此签名是要用作mux端点的处理程序的实际签名。解决方案包括定义一个
处理程序
类型,该类型是一个具有三个字段的结构:

handlerType
:将其视为值为
“CREATE”
“GET”
的枚举。这将决定您粘贴在问题中的两块代码中我们应该使用哪一块

handlerActionName
:这将告诉
“创建”
“获取”
哪些是可弹性使用的。值应该是
“flow”
“process”

elastible
:这将是具有
SetESID
功能的接口类型
elastible
。我们将使用它将
流程
类型发送到
处理程序
。因此,
过程
都应该满足我们的接口。 这将使解决方案更加通用,只调用
handler.elastible.SetESID()
,我们将插入ESID,而不管“elastible”中的底层类型是“Flow”还是“Process”

我还定义了一个
sendResponse(响应接口{})
函数,我们将继续使用它来发送响应。它使用闭包获取
whttp.ResponseWriter
<代码>响应
因此可以是任何内容,例如

struct {
    Highest int `json:"id"`
}{highest} 
过程
。这也将使该函数成为通用函数

现在,完整的解决方案是

// This is the type that will be used to build our handlers.
type Handler struct {
    handlerType       string    // Can be "CREATE" or "GET"
    handlerActionName string    // Can be "flow" or "process"
    elastible         Elastible // Can be *Flow or *Process
}

// Your ESID Type.
type ESIDType string

// Solution proposed by https://stackoverflow.com/users/7426/adrian.
type Elastible interface {
    SetESID(id ESIDType)
}

// Make the Flow and Process pointers implement the Elastible interface.
func (flow *Flow) SetESID(id ESIDType) {
    flow.ESID = id
}

func (process *Process) SetESID(id ESIDType) {
    process.ESID = id
}

// Create a Higher Order Function which will return the actual handler.
func CreateHandler(handler Handler) func(*AppContext, http.ResponseWriter, http.Request) (int, error) {

    return func(a *AppContext, w http.ResponseWriter, r http.Request) (int, error) {

        // Define a sendResponse function so that we may not need to copy paste it later.
        // It captures w using closure and takes an interface argument that we use to call .Encode() with.

        sendResponse := func(response interface{}) (int, error) {
            b := new(bytes.Buffer)
            json.NewEncoder(b).Encode(response)
            w.Header().Set("Content-Type", "application/json")
            w.Write(b.Bytes())
            return 200, nil
        }

        // Define these variables beforehand since we'll be using them
        // in both the if and else block. Not necessary really.
        var code int
        var err error

        // Check the handlerType. Is it create or get?
        if handler.handlerType == "CREATE" {
            var highest int

            // Creates the thing using handler.handlerActionName which may be "flow" or "process"
            highest, code, err = a.Create(handler.handlerActionName, r)
            if code != 200 || err != nil {
                return code, err
            }

            // Send the response using the above defined function and return.
            return sendResponse(struct {
                Highest int `json:"id"`
            }{highest})

        } else {

            // This is GET handlerType.
            var hit HitType

            // Get the hit using again the handler.handlerActionName which may be "flow" or "process"
            hit, code, err = a.GetByID(handler.handlerActionName, mux.Vars(r)["id"], r)
            if code != 200 || err != nil {
                return code, err
            }

            // Do the un-marshalling.
            err = json.Unmarshal(*hit.Source, ob)
            if err != nil {
                return 500, err
            }

            // We have set the handler.elastible to be an interface type
            // which will have the SetESID function that will set the ESID in the
            // underlying type that will be passed on runtime.
            // So the ESID will be set for both the Flow and the Process types.
            // This interface idea was given inside an earlier answer by
            // https://stackoverflow.com/users/7426/adrian

            handler.elastible.SetESID(hit.id)
            return sendResponse(handler.elastible)
        }
    }
}
您可以使用以下代码设置mux端点

    // This was your first function. "CreateFlow"
    api.Handle("/createFlow/{id:[0-9]+}", handlers.AppHandler{
        context, CreateHandler(Handler{
            elastible:         &Flow{},
            handlerActionName: "flow",
            handlerType:       "CREATE",
        }),
    }).Methods("GET")

    // This was your second function. "CreateProcess"
    api.Handle("/createProcess/{id:[0-9]+}", handlers.AppHandler{
        context, CreateHandler(Handler{
            elastible:         &Process{},
            handlerActionName: "process",
            handlerType:       "CREATE",
        }),
    }).Methods("GET")

    // This was your third function. "GetFlow"
    api.Handle("/getFlow/{id:[0-9]+}", handlers.AppHandler{
        context, CreateHandler(Handler{
            elastible:         &Flow{},
            handlerActionName: "flow",
            handlerType:       "GET",
        }),
    }).Methods("GET")

    // This was your fourth function. "GetProcess"
    api.Handle("/getProcess/{id:[0-9]+}", handlers.AppHandler{
        context, CreateHandler(Handler{
            elastible:         &Process{},
            handlerActionName: "process",
            handlerType:       "GET",
        }),
    }).Methods("GET")


希望有帮助

太棒了。谢谢你的回复。我会在实施后更新。非常感谢您的详细回复。我将尝试实现您的建议(Adrian的超集),然后再回复您。为了简单起见,我省略了其他REST调用,有删除和必要的选项。也得到了一个大集。我将花一些时间来尝试和概括。阿德里安的解决方案似乎更简单,更容易理解/维护。在这里更通用似乎是以复杂性/可读性为代价的,除非你真的需要,否则IMO不值得。当然,99%的时间雅格尼原则适用:-)雅格尼原则将适用于如果我得到这个项目的报酬;)