Обработчики Golang обрабатывающие различные типы
Это Аппхандлеры из шаблона, который я нашел в интернете, исследуя gorilla/mux. Они являются частью структуры, которая удовлетворяет http.Обработчик. Если вы заметили, следующие два блока точно такие же. Фактически, они могут быть переданы "варианту" ("потоку" или "процессу") в виде строки.
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
}
Однако следующие два блока нуждаются не только в строке, но и в переменной соответствующего типа ("поток" и "процесс"), чтобы успешно отменить попадание, которое я получаю от 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, когда речь идет об объявленном типе. Эти обработчики тоже находятся в одном пакете, так как я думаю, что все они выполняют одну и ту же задачу. Я очень четко повторяю себя в коде, но мне нужен совет о том, как я могу улучшить. Я прошел мимо "немного копирования лучше, чем немного зависимости."но я боюсь, потому что "отражение никогда не бывает ясным".
Вот пример: пример объявления в main с использованием одной из этих функций.
api.Handle("/flow/{id:[0-9]+}", handlers.AppHandler{context, handlers.GetFlow}).Methods("GET")
2 ответа:
Хорошо, я предлагаю решение, которое даст вам максимальное повторное использование кода и минимальное копирование кода. Это, на мой взгляд, самое общее решение. Мы также примем во внимание ответ, данный https://stackoverflow.com/users/7426/adrian для завершения решения. Вам нужно только определить одну функцию, которая будет функцией более высокого порядка
CreateHandler
, которая будет возвращать функцию следующей сигнатуры:func(*AppContext, http.ResponseWriter, http.Request) (int, error)
.Эта подпись является фактической подпись обработчика, который должен использоваться в качестве конечной точки mux. Решение включает в себя определение типа
Handler
, который является структурой, имеющей три поля:•
handlerType
: думайте об этом как о перечислении, имеющем либо значение"CREATE"
, либо"GET"
. Это позволит решить, какой из двух блоков кода, которые вы вставили в свой вопрос, мы должны использовать.•
handlerActionName
: это подскажет"CREATE"
или"GET"
, какой эластичный использовать. Значение должно быть либо"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
. Это также сделает эту функцию универсальной.Полное решение было бы теперь.
// 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")
Надеюсь, это поможет!
Вы можете сделать это, передав пример нужного типа, так же, как это делает
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 defs или другого зависимого кода), но я надеюсь, что он донесет идею.