Golang中如何避免代码重复
Golang中如何避免代码重复 我正在开发的应用通过JSON编码在网络连接中传递结构体。
有多个请求和响应结构体,我想编写一个通用的Marshal函数,能够接收任意结构体,进行序列化后返回字符串。
我可能用错术语了,但在这个示例中,我创建了一个带有Marshal函数的基础结构体,Request1包含(?)了它。运行Marshal函数不会返回任何数据。我知道这是因为Request1中的属性都不在基础结构体中,所以函数运行时看不到这些属性。我理解为什么这不起作用。
Request2有自己独立的Marshal函数,运行正常。
有没有办法实现某种类型的继承,这样我就能使用单一的Marshal函数,而不必为每个请求和响应结构体重复相同的代码?
package main
import (
"encoding/json"
"fmt"
)
type base struct{}
func (b *base) Marshal() string {
ret, _ := json.Marshal(b)
return string(ret)
}
type Request1 struct {
base
Name string
}
type Request2 struct {
Name string
}
func (r *Request2) Marshal() string {
ret, _ := json.Marshal(r)
return string(ret)
}
func main() {
r1 := Request1{}
r1.Name = "Request1"
fmt.Printf("R1: %s\n", r1.Marshal())
r2 := Request2{}
r2.Name = "Request2"
fmt.Printf("R2: %s\n", r2.Marshal())
}
更多关于Golang中如何避免代码重复的实战教程也可以访问 https://www.itying.com/category-94-b0.html
Request1 没有 Marshall 方法,你只是在编组 JSON 消息,而根据定义它是空的。
更多关于Golang中如何避免代码重复的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
是的,我理解这一点,我想了解的是如何为所有内容提供一个 Marshal 函数,而不必重复所有代码。
func main() {
fmt.Println("hello world")
}
如果不编写这样的方法,而是使用注解:
type Foo struct {
Field1 string `json:"field"`
}
作为一名有Ruby背景的人,这对我来说确实看起来更优雅。
如果你不想重复代码,一个简单的方法是在实际函数上使用包装函数。你只需要重复包装器部分。
以下是在Go语言中实现这些功能的正确方式,还是说它们只是些取巧的做法?这两种方式看起来都不像我所熟悉的那种优雅流畅的Go代码风格。
我刚读了几篇关于注解的文章,但不太理解它们在这里能起到什么作用。你知道有什么示例能展示我所需的使用方式吗?
这难道不是和@NobbZ的方法一样吗?只不过省去了声明JSONMessageMarker的步骤,直接将其放入函数定义中?
好的,我会尝试使用这两种方法。
目前,我需要能够传入我的结构体并获取一个字符串输出,然后使用 fmt.Fprintf 将其作为 HTTP 负载发送。
纯属娱乐,带包装函数的版本。
嗯,我的版本确实直接返回 string 而不是 []byte,不确定你更喜欢哪个。而且我的版本比单纯使用 interface{} 在类型安全方面更好一些。最终你需要根据项目需求自行选择更适合的方案。
这解释了 foo() 函数的作用,谢谢。我原以为这只是演示我可以在 JSONMessage 结构体上添加需要共享的函数。
在这种情况下,类型安全性并不太重要,因为我相当确定传入的任何内容都应该能够被序列化。但为了确保我不会出错或传入不应该传递的内容,我可能会坚持使用 @NobbZ 的版本。
BB-8 说: 这是个好主意,请记住,你需要确保任何希望通过
MarshallJSONMessage进行序列化的内容都必须实现foo及其他相关接口方法。
这样很好,它能确保我正确构建所有结构体,并且只传递正确的内容。
我会仔细阅读文档,但现在我明白了它的工作原理,这确实很有意义。
感谢您的帮助。
这正是我想要的,谢谢。我打算使用完整版本,但为了理解其原理,我将其简化了。
我的理解是否正确:只要传入的内容能够被序列化,就可以传递任何类型?而接口作为一种通用类型实现了这一点?
func main() {
fmt.Println("hello world")
}
或许您可以考虑使用完全通用的方案,这样就能尽可能避免处理序列化问题。您听说过gRPC吗?它的速度非常快,并且可以使用JSON作为消息格式:https://grpc.io/blog/grpc-with-json
已经有人为Go语言创建了JSON编解码器,这里有一个包含示例的链接:https://jbrandhorst.com/post/grpc-json/
祝您有美好的一天。
digininja:
我刚读了几篇关于注解的文章,但无法理解它们在这里能起到什么帮助
是的,你说得对,我误解了你的问题,直到现在(在我按顺序睡了一觉并喝了咖啡之后)才意识到问题不在于编组本身,而是需要一种简单的方法将数据编组成字符串。
所以你可以通过一些接口技巧来实现这一点:Go Playground - Go编程语言
func main() {
fmt.Println("hello world")
}
这是个不错的主意,请记住,你需要确保任何希望通过 MarshallJSONMessage 进行序列化的内容都必须实现 foo 接口及其他相关接口方法。
如果你对接口不熟悉,可以通过以下链接快速了解接口的概念:https://gobyexample.com/interfaces 和 http://www.golangbootcamp.com/book/interfaces。
在研究接口时,查阅 Go 原生库也极为有用,特别建议关注 Reader 和 Writer 接口——它们被广泛使用且设计极为简洁(如果我没记错的话,这两个接口分别只包含 Read 和 Write 单个方法)。
我可能误解了这里的问题。
@NobbZ 的回复似乎是解决您遇到的问题的方案,但如果您想要序列化任意结构体,这里有一个解决方案。
func Marshal(input interface{}) (encoded []byte, err error) {
encoded, err = json.MarshalIndent(input, "", "\t")
return
}
这将尝试编码并返回 input 的格式化 JSON。如果您希望返回字符串类型,可以转换结果值或将上述代码更新为以下版本:
func Marshal(input interface{}) (string, error) {
encoded, err := json.MarshalIndent(input, "", "\t")
if err != nil {
return "", err
}
return string(encoded), err
}
“最终你需要根据自己的项目需求来选择更适合的方案。”
确实如此!
不过值得一提的是,你发布了示例的修订版本:https://play.golang.org/p/TAWyw41WdWd
在那个版本中,你移除了类型安全性(foo方法),因为JSONMessageMarker不再实现任何方法,这意味着最终你会得到与使用interface{}类似的结果。
例如,以下代码在你的版本中能够无报错执行:
s3, _ := MarshallJSONMessage([]string{ "hello", "world" })
fmt.Printf("Marshalled: %s\n", s3) // Marshalled: ["hello","world"]
然而在最初发布的版本中,你会得到一个错误,因为[]string类型没有实现foo方法,这符合预期。要获得类型安全性,你需要确保JSONMessageMarker接口实际实现了某些方法。
希望这些说明能有所帮助。
在Go语言中,可以通过接口和反射来实现通用的序列化功能,避免为每个结构体重复编写Marshal方法。以下是几种解决方案:
方案1:使用接口和类型断言
package main
import (
"encoding/json"
"fmt"
)
type Marshaller interface {
Marshal() string
}
type Request1 struct {
Name string
}
type Request2 struct {
Name string
Age int
}
// 通用Marshal函数
func Marshal(v interface{}) string {
data, _ := json.Marshal(v)
return string(data)
}
// 为所有需要序列化的类型实现Marshaller接口
func (r *Request1) Marshal() string {
return Marshal(r)
}
func (r *Request2) Marshal() string {
return Marshal(r)
}
func main() {
r1 := &Request1{Name: "Request1"}
r2 := &Request2{Name: "Request2", Age: 25}
fmt.Printf("R1: %s\n", r1.Marshal())
fmt.Printf("R2: %s\n", r2.Marshal())
}
方案2:使用反射创建通用包装器
package main
import (
"encoding/json"
"fmt"
"reflect"
)
type Marshaller interface {
Marshal() string
}
type GenericMarshaller struct {
Data interface{}
}
func (gm *GenericMarshaller) Marshal() string {
data, _ := json.Marshal(gm.Data)
return string(data)
}
type Request1 struct {
Name string
}
type Request2 struct {
Name string
Age int
}
func main() {
r1 := &Request1{Name: "Request1"}
r2 := &Request2{Name: "Request2", Age: 25}
m1 := &GenericMarshaller{Data: r1}
m2 := &GenericMarshaller{Data: r2}
fmt.Printf("R1: %s\n", m1.Marshal())
fmt.Printf("R2: %s\n", m2.Marshal())
}
方案3:使用函数式方法
package main
import (
"encoding/json"
"fmt"
)
type Request1 struct {
Name string
}
type Request2 struct {
Name string
Age int
}
// 通用Marshal函数
func MarshalJSON(v interface{}) string {
data, err := json.Marshal(v)
if err != nil {
return "{}"
}
return string(data)
}
func main() {
r1 := Request1{Name: "Request1"}
r2 := Request2{Name: "Request2", Age: 25}
fmt.Printf("R1: %s\n", MarshalJSON(r1))
fmt.Printf("R2: %s\n", MarshalJSON(r2))
}
方案4:使用嵌入和接口组合
package main
import (
"encoding/json"
"fmt"
)
type JSONMarshaller struct{}
func (jm *JSONMarshaller) MarshalJSON() ([]byte, error) {
return json.Marshal(jm)
}
func (jm *JSONMarshaller) Marshal() string {
data, _ := jm.MarshalJSON()
return string(data)
}
type Request1 struct {
JSONMarshaller
Name string
}
type Request2 struct {
JSONMarshaller
Name string
Age int
}
func main() {
r1 := &Request1{Name: "Request1"}
r2 := &Request2{Name: "Request2", Age: 25}
fmt.Printf("R1: %s\n", r1.Marshal())
fmt.Printf("R2: %s\n", r2.Marshal())
}
推荐使用方案1或方案3,它们提供了清晰的接口设计和代码复用,同时保持了Go语言的惯用风格。方案1通过接口提供了更好的类型安全,而方案3则更加简洁直接。

