全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2024-08-24_探索 Go 标准库中的优雅设计模式:函数与接口的结合

您的位置:首页 >> 新闻 >> 行业资讯

探索 Go 标准库中的优雅设计模式:函数与接口的结合 点击关注公众号,“技术干货”及时达!前言在 Go 语言的标准库(特别是在 net/http 包中),我们会发现一种非常优雅且简洁的编程模式,即通过函数类型与接口的结合来实现灵活且可扩展的代码。这不仅仅在代码复用、简化测试方面带来了极大方便,还提升了整体的开发效率和代码质量。这篇文章我们将探讨这一模式的工作原理及其诸多好处。 基础概念首先,我们需要理解 Go 语言中的几个基础概念: 接口(Interface):在 Go 中接口是一组方法的集合,任何实现了这些方法的类型都可以被认为实现了这个接口。类型定义(Type Definition):Go 语言允许定义新的类型。例如,可以将某种函数签名定义为一种类型。方法(Method):可以为某个类型定义方法。该类型可以是结构体、基础类型甚至是函数类型。实战分析我们来看 net/http 包中是如何使用这些概念构建一个非常灵活的 HTTP 服务器的。 先看一张图,总览一下这个灵活的 HTTP 服务器: net/http 包中的设计?Go 语言源代码 net/http/server.go 版本:Go 1.22.2 ?首先来看看标准库中 Handler 接口的定义: typeHandlerinterface{ ServeHTTP(ResponseWriter,*Request) } 任何实现了 ServeHTTP 方法的类型都可以被用作 HTTP 请求的处理器。 一般情况下,我们都习惯于使用一个结构体来实现一个接口,尽管可能使用空的结构体,例如: typeMyHandlerstruct{} func(h*MyHandler)ServeHTTP(whttp.ResponseWriter,r*http.Request){ w.Write([]byte("helloworld")) } 而这样无疑会增加很多重复且无用的代码,因为我们并不关注 MyHandler 结构体。因此,我们不想每次都去定义一个结构体和绑定方法,能不能使用简单的函数实现? 那么,我们如何将一个普通的函数转变为符合 Handler 接口的类型呢? 这就需要用到以下的代码模式,我们接着来看源码: typeHandlerFuncfunc(ResponseWriter,*Request) func(fHandlerFunc)ServeHTTP(wResponseWriter,r*Request){ f(w,r) } 这里定义了一种新的函数类型 HandlerFunc,其签名与 ServeHTTP 方法一致。并为其实现了 ServeHTTP 方法,这样就使得所有这种类型的函数都符合 Handler 接口。 那这种模式具体该怎么使用呢? 实战代码我们通过具体的代码示例来更好地理解这一模式的使用: packagemain import( "net/http" "fmt" ) //一些通用的逻辑封装 funchelloHandler(whttp.ResponseWriter,r*http.Request){ w.Write([]byte("helloworld")) } funcmain(){ //http.HandlerFunc(helloHandler)将helloHandler函数转换为HandlerFunc类型 http.Handle("/hello",http.HandlerFunc(helloHandler)) //或者直接使用http.HandleFunc,效果完全相同 http.HandleFunc("/hello",helloHandler) http.ListenAndServe(":8080",nil) } 在这个例子中: 我们定义了一个处理请求的函数 helloHandler;然后通过 http.Handle 将 url 与处理函数绑定起来,而 http.Handle 函数需要一个实现了 Handler 接口的对象;我们通过 http.HandlerFunc(helloHandler) 将其转换为 HandlerFunc 类型,这样 helloHandler 就自动实现了 ServeHTTP 方法,从而成为一个合法的 Handler;我们还可以使用 http.HandleFunc 直接将函数关联到某个路径,这其实是标准库对上述转换的简单封装。我们点进去看一眼源码: funcHandleFunc(patternstring,handlerfunc(ResponseWriter,*Request)){ //版本控制,暂时不用关注 ifuse121{ DefaultServeMux.mux121.handleFunc(pattern,handler) }else{ DefaultServeMux.register(pattern,HandlerFunc(handler)) } } funcHandle(patternstring,handlerHandler){ //版本控制,暂时不用关注 ifuse121{ DefaultServeMux.mux121.handle(pattern,handler) }else{ DefaultServeMux.register(pattern,handler) } } 源码底层实现就是将 “路径(url) + 处理器” 注册到 DefaultServeMux(默认的 HTTP 请求多路复用器),这样我们使用 url 访问 web 请求时,就可以路由到对应的处理器进行处理了。 这样就轻松实现了将一个简单处理函数,注册进默认的 HTTP 路由器中的逻辑,如果需要多个处理函数,也可以轻松实现,而不用为每一个处理函数都定义一个空的结构体,然后绑定方法,这样代码会更加简洁和易读。 http.Handle("/hello",http.HandlerFunc(helloHandler)) http.Handle("/hello",http.HandlerFunc(helloHandler2)) http.Handle("/hello",http.HandlerFunc(helloHandler3)) 如果你仔细观察,会发现 http.ListenAndServe 的第二个参数也是接口类型 Handler,我们使用了标准库 net/http 内置的路由,因此传入的值是 nil。 http.ListenAndServe(":8080",nil) 标准库 net/http 内置的路由为 ServeMux,本身也实现了 ServeHTTP 方法,也是一个合法的 Handler。 typeServeMuxstruct{ musync.RWMutex treeroutingNode indexroutingIndex patterns[]*pattern//TODO(jba):removeifpossible mux121serveMux121//usedonlywhenGODEBUG=httpmuxgo121=1 } func(mux*ServeMux)ServeHTTP(wResponseWriter,r*Request){ ifr.RequestURI=="*"{ ifr.ProtoAtLeast(1,1){ w.Header().Set("Connection","close") } w.WriteHeader(StatusBadRequest) return } varhHandler ifuse121{ h,_=mux.mux121.findHandler(r) }else{ h,_,r.pat,r.matches=mux.findHandler(r) } h.ServeHTTP(w,r) } 它的主要作用就是,通过传入的 url 匹配对应的处理函数 mux.findHandler(我们刚刚注册进去的 ),然后通过 h.ServeHTTP(w, r) 实现对不同 url 的处理逻辑,这样一个灵活的 HTTP 服务器就搭建完成了,通过这个 http.ListenAndServe(":8080", nil) 就可以轻松启动起来(具体内部如何建立连接,处理请求不是本文重点,后续再详细讲解源码)。 一个思考问题:那如果这个地方我们传入的是一个实现了 Handler 接口的结构体呢? 是不是就可以完全托管所有的 HTTP 请求,后续怎么路由,怎么处理,请求前后增加什么功能,都可以自定义了,慢慢地,就变成了一个功能丰富的 Web 框架了。(有兴趣的可以先看看 gin 框架,后续我们有机会也详细聊一下一个功能丰富的 Web 框架如何搭建) 结论通过了解和实践这种编程模式,我们可以更高效地编写简洁、灵活且高质量的代码,这不仅提高了开发效率,也使得代码更加易于维护和扩展。 除了代码简洁,这种模式还有什么优点呢? 接下来就总结一下这种模式的好处: 简洁性与可读性:通过这种方式,我们可以轻松地将简单的函数转换为复杂接口的实现。这使得代码更加简洁和直观,易于维护。灵活性:这种设计模式允许开发者在处理不同需求时能够灵活调整代码。通过定义不同的方法和处理器,能够轻松适应变化。减少样板代码:我们不必每次都去定义一个结构体和绑定方法,只需编写符合特定签名的函数即可。这减少了样板代码,让我们可以将更多时间花费在业务逻辑上。增强代码复用性:由于函数可以非常方便地转换为实现接口的类型,这使得代码模块化和复用性变得更加容易。我们可以编写通用函数来处理许多场景,然后通过这种模式组合实际需求。简化测试:函数的单元测试通常更为容易。通过这种模式,我们可以直接对函数进行测试,然后再将其集成到实际应用中,从而简化了测试流程。希望通过这篇文章,大家能更好地理解 Go 语言中函数类型和接口结合所带来的强大之处,并在实际开发中合理运用这一模式。 最后举个简单的例子,自己领悟一下“代码的整洁之道”: packagemain import"fmt" //定义一个操作接口 typeOperationinterface{ Execute(a,bint)int } //使用一个类型,将函数适配成符合接口的方法 typeOperationFuncfunc(a,bint)int //让函数类型实现接口 func(fOperationFunc)Execute(a,bint)int{ returnf(a,b) } //计算函数,接受一个Operation接口 funcCalculate(a,bint,opOperation)int{ returnop.Execute(a,b) } funcmain(){ //定义一些操作函数 addition:=OperationFunc(func(a,bint)int{ returna+b }) subtraction:=OperationFunc(func(a,bint)int{ returna-b }) //使用操作函数进行计算 fmt.Println("Addition:",Calculate(10,5,addition))//Output:Addition:15 fmt.Println("Subtraction:",Calculate(10,5,subtraction))//Output:Subtraction:5 } 点击关注公众号,“技术干货”及时达! 阅读原文

上一篇:2024-01-19_「转」【重磅】2023影视幕后从业者生态调查报告 下一篇:2025-06-03_北面,一个户外圈的潮流老炮儿

TAG标签:

18
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价