英文:
Golang handlers handling different types
问题
这是我在研究gorilla/mux时在网上找到的一个模式的AppHandlers。它们是一个满足http.Handler接口的结构体的一部分。如果你注意到,下面的两个代码块是完全相同的。实际上,它们可以通过一个字符串参数("flow"或"process")来传递。
然而,下面的两个代码块不仅需要一个字符串参数,还需要与之关联的类型("Flow"和"Process")的变量,以成功地从ElasticSearch中解析得到的数据。除此之外,它们的代码是相同的。
不确定如何在golang中泛化这种行为,特别是当涉及到已声明的类型时。这些处理程序都在同一个包中,因为我认为它们都在完成类似的任务。我在代码中明显地重复自己,但我需要一些建议,看看如何改进。我已经超越了"少依赖胜过少复制"的原则,但我担心"反射从来都不是清晰的"。
以下是在main函数中使用其中一个函数的声明示例:
api.Handle("/flow/{id:[0-9]+}", handlers.AppHandler{context, handlers.GetFlow}).Methods("GET")
英文:
These are AppHandlers from a pattern I found online while researching gorilla/mux. They part of a struct that satisfies http.Handler. If you notice, the following two blocks are exactly the same. Effectively, they could be passed the 'variant' ("flow" or "process") as a string.
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
}
However, the following two blocks not only need the string, but they need a variable of the associated type ("Flow" and "Process") to successfully Unmarshal the hit I get from ElasticSearch. Other than that, they are Identical code.
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
}
I am not sure how to generalize this behavior in golang when there is a declared type involved. These handlers are all in the same package too, as I think that they are all accomplishing a similar task. I am very clearly repeating myself in code but I need advice on how I can improve. I've gone past "a little copying is better than a little dependency." but I am afraid because "reflection is never clear".
Here is an example of the declaration in main using one of these functions.
api.Handle("/flow/{id:[0-9]+}", handlers.AppHandler{context, handlers.GetFlow}).Methods("GET")
答案1
得分: 6
你可以通过传入所需类型的示例来实现,就像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) // ESIDType是任意类型,根据示例中的代码无法确定具体类型
}
func (f *Flow) SetESID(id ESIDType) {
f.ESID = id
}
这段代码未经测试(因为我没有你的结构定义或其他相关代码),但我希望能够传达出思想。
英文:
You can do it by passing in an exemplar of the necessary type, the same way that Unmarshal
does it:
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
}
This code is untested (because I don't have your struct defs or other dependent code) but I hope it gets the idea across.
答案2
得分: 2
好的,以下是翻译好的内容:
好的,我提出了一个解决方案,它将最大限度地重用代码并最小化代码复制。在我看来,这绝对是最通用的解决方案。我们还将考虑https://stackoverflow.com/users/7426/adrian给出的答案来完成解决方案。你只需要定义一个单一函数,它将是一个高阶函数CreateHandler
,它将返回一个具有以下签名的函数:
func(*AppContext, http.ResponseWriter, http.Request) (int, error)
。
这个签名是要用作mux端点的处理程序的实际签名。解决方案涉及定义一个Handler
类型,它是一个具有三个字段的结构体:
• handlerType
:将其视为一个枚举,其值可以是"CREATE"
或"GET"
。这将决定我们应该使用你在问题中粘贴的两个代码块中的哪一个。
• handlerActionName
:这将告诉"CREATE"
或"GET"
要使用的Elastible
。值应为"flow"
或"process"
。
• elastible
:这将是一个接口类型Elastible
,它将具有SetESID
函数。我们将使用它将我们的Flow
或Process
类型发送到我们的Handler
。因此,Flow
和Process
都应满足我们的接口。
这将使解决方案更加通用,并且只需调用handler.elastible.SetESID()
,我们就可以插入ESID,而不管elastible
中的底层类型是Flow
还是Process
。
我还定义了一个sendResponse(response interface{})
函数,我们将重用它来发送响应。它使用闭包获取w http.ResponseWriter
。因此,response
可以是任何东西,例如:
struct {
Highest int `json:"id"`
}{highest}
或者是Flow
或Process
。这也使得这个函数变得通用。
完整的解决方案现在是:
// 这是用于构建我们的处理程序的类型。
type Handler struct {
handlerType string // 可以是"CREATE"或"GET"
handlerActionName string // 可以是"flow"或"process"
elastible Elastible // 可以是*Flow或*Process
}
// 你的ESID类型。
type ESIDType string
// https://stackoverflow.com/users/7426/adrian提出的解决方案。
type Elastible interface {
SetESID(id ESIDType)
}
// 使Flow和Process指针实现Elastible接口。
func (flow *Flow) SetESID(id ESIDType) {
flow.ESID = id
}
func (process *Process) SetESID(id ESIDType) {
process.ESID = id
}
// 创建一个高阶函数,它将返回实际的处理程序。
func CreateHandler(handler Handler) func(*AppContext, http.ResponseWriter, http.Request) (int, error) {
return func(a *AppContext, w http.ResponseWriter, r http.Request) (int, error) {
// 定义一个sendResponse函数,以便我们以后不需要复制粘贴它。
// 它使用闭包捕获w,并接受一个我们用于调用.Encode()的接口参数。
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
}
// 预先定义这些变量,因为我们将在if和else块中使用它们。
// 实际上并不是必需的。
var code int
var err error
// 检查handlerType。是create还是get?
if handler.handlerType == "CREATE" {
var highest int
// 使用handler.handlerActionName(可能是"flow"或"process")创建该对象。
highest, code, err = a.Create(handler.handlerActionName, r)
if code != 200 || err != nil {
return code, err
}
// 使用上面定义的函数发送响应并返回。
return sendResponse(struct {
Highest int `json:"id"`
}{highest})
} else {
// 这是GET处理程序类型。
var hit HitType
// 使用handler.handlerActionName(可能是"flow"或"process")获取hit。
hit, code, err = a.GetByID(handler.handlerActionName, mux.Vars(r)["id"], r)
if code != 200 || err != nil {
return code, err
}
// 进行解组。
err = json.Unmarshal(*hit.Source, ob)
if err != nil {
return 500, err
}
// 我们已经将handler.elastible设置为一个接口类型,
// 它将具有SetESID函数,该函数将在运行时设置ESID。
// 因此,Flow和Process类型都将设置ESID。
// 这个接口的想法是在早期的答案中提出的,
// 由https://stackoverflow.com/users/7426/adrian提供。
handler.elastible.SetESID(hit.id)
return sendResponse(handler.elastible)
}
}
}
然后,你可以使用以下代码设置你的mux端点。
// 这是你的第一个函数。"CreateFlow"
api.Handle("/createFlow/{id:[0-9]+}", handlers.AppHandler{
context, CreateHandler(Handler{
elastible: &Flow{},
handlerActionName: "flow",
handlerType: "CREATE",
}),
}).Methods("GET")
// 这是你的第二个函数。"CreateProcess"
api.Handle("/createProcess/{id:[0-9]+}", handlers.AppHandler{
context, CreateHandler(Handler{
elastible: &Process{},
handlerActionName: "process",
handlerType: "CREATE",
}),
}).Methods("GET")
// 这是你的第三个函数。"GetFlow"
api.Handle("/getFlow/{id:[0-9]+}", handlers.AppHandler{
context, CreateHandler(Handler{
elastible: &Flow{},
handlerActionName: "flow",
handlerType: "GET",
}),
}).Methods("GET")
// 这是你的第四个函数。"GetProcess"
api.Handle("/getProcess/{id:[0-9]+}", handlers.AppHandler{
context, CreateHandler(Handler{
elastible: &Process{},
handlerActionName: "process",
handlerType: "GET",
}),
}).Methods("GET")
希望对你有所帮助!
英文:
Alright, I propose a solution that will give you the maximum code reuse and minimum code copying. This, in my opinion, is by far the most generic solution. We will also take into account the answer given by https://stackoverflow.com/users/7426/adrian to complete the solution. You only have to define a single function which will be a higher order function CreateHandler
which will return a function of the following signature:
<br />func(*AppContext, http.ResponseWriter, http.Request) (int, error)
.
This signature is the actual signature of the handler that is to be used as a mux end point. The solution involves defining a Handler
type which is a struct having three fields:
• handlerType
: Think of it as an enum having either a value of "CREATE"
or "GET"
. This will decide which among the two blocks of code that you pasted in your question should we use.
• handlerActionName
: This will tell the "CREATE"
or "GET"
which Elastible to use. Value should either be "flow"
or "process"
.
• elastible
: This will the Interface type Elastible
that will have the SetESID
function. We will use this to send our Flow
or Process
types to our Handler
. Thus both Flow
and Process
should satisfy our interface.
This will make the solution even more generic and will only calling handler.elastible.SetESID()
and we will have inserted the ESID irrespective of that fact the underlying type in 'elastible' can either be 'Flow' or a 'Process'
I also define a sendResponse(response interface{})
function that we will resuse to send the response. It acquires w http.ResponseWriter
using closure. response
can thus be anything, a
struct {
Highest int `json:"id"`
}{highest}
or a Flow
or a Process
. This will make this function generic too.
The complete solution would now be.
// 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)
}
}
}
And you would setup your mux end points using the following code.
// 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")
<br />
Hope it helps!
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论