Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Go语言动手写Web框架 - Gee第四天 分组控制Group | 极客兔兔 #44

Open
geektutu opened this issue Sep 1, 2019 · 62 comments

Comments

@geektutu
Copy link
Owner

geektutu commented Sep 1, 2019

https://geektutu.com/post/gee-day4.html

7天用 Go语言 从零实现Web框架教程(7 days implement golang web framework from scratch tutorial),用 Go语言/golang 动手写Web框架,从零实现一个Web框架,以 Gin 为原型从零设计一个Web框架。本文介绍了分组控制(Group Control)的意义,以及嵌套分组路由的实现。

@wzc314
Copy link

wzc314 commented Feb 22, 2020

很好的入门文章!感谢博主!

Day4中的Engine定义有一点疑问请教:

Engine struct {
	*RouterGroup
	router *router
	groups []*RouterGroup // store all groups
}

这个RouterGroup是Engine的一个属性吗?

Go接触不久,还请指教。

@evanxg852000
Copy link

Wonderful article series, Thanks for sharing the knowledge.
I cannot read the text but I have been using google translate to follow along & code.
This type of article helps understand the concept without all the noise of a big project
or optimisation.
Please can you write an article and implementing a B+ tree for key value store in Go?
a bit like the one in BoltDB.

@evanxg852000
Copy link

@wzc314
很好的入门文章!感谢博主!

Day4中的Engine定义有一点疑问请教:

Engine struct {
	*RouterGroup
	router *router
	groups []*RouterGroup // store all groups
}

这个RouterGroup是Engine的一个属性吗?

Go接触不久,还请指教。

This is called embeded type in Go
Read the section Embeding of this guide https://github.com/hoanhan101/ultimate-go

@geektutu
Copy link
Owner Author

geektutu commented Feb 22, 2020

@evanxg852000 I'm very glad to see your comment. I'm sorry and I'm busy writing the 3rd series, a ORM framework geeorm now. There is a golang version for bolt, named bbolt. I would like to imitate bbolt to implement geebolt, maybe in several weeks. It's a wonderful choice as the 4th, thanks for your suggestion.

@geektutu
Copy link
Owner Author

geektutu commented Feb 22, 2020

@wzc314 这是 go 中的嵌套类型,类似 Java/Python 等语言的继承。这样 Engine 就可以拥有 RouterGroup 的属性了。

@201732110125
Copy link

感谢博主!
我想问一下,RouteGroup结构体中的parent属性是否可以删去,
因为在使用的的过程中,好像并没有需要 获取一个group的parent的地方。

@geektutu
Copy link
Owner Author

geektutu commented Mar 6, 2020

@201732110125 parent 之前设计是用来拼接 prefix 的,每个 group 只记录自己的部分,最后通过 parent 层层回溯拼接。不过后来改用 group.prefix + prefix 的方式 group 初始化时已经拼接了完整的 prefix,所以不需要 parent 了,可以删除。

@xenv
Copy link

xenv commented Apr 19, 2020

我认为这里的 RouterGroup 应该嵌套 Engine 而不是反过来,gee.New() 的时候初始化一个 Engine,然后返回一个持有自己的 RouterGroup 就 OK。当然差别也不是特别大,不过从面向对象的角度舒服一点。

func New() *RouterGroup {
	engine := &Engine{router: newRouter()}
	return &RouterGroup{
		Engine: engine,
	}
}

func (group *RouterGroup) Group(prefix string) *RouterGroup {
	newGroup := &RouterGroup{
		prefix: group.prefix + prefix,
		Engine: group.Engine,
	}
	group.Engine.groups = append(group.Engine.groups , newGroup)
	return newGroup
}

@geektutu
Copy link
Owner Author

geektutu commented Apr 19, 2020

@xenv Go语言的嵌套在其他语言中类似于继承,子类必然是比父类有更多的成员变量和方法。RouterGroup 仅仅是负责分组路由,Engine 除了分组路由外,还有很多其他的功能。RouterGroup 继承 Engine 的 Run()ServeHTTP 等方法是没有意义的。

@SourceLink
Copy link

@geektutu
@xenv Go语言的嵌套在其他语言中类似于继承,子类必然是比父类有更多的成员变量和方法。RouterGroup 仅仅是负责分组路由,Engine 除了分组路由外,还有很多其他的功能。RouterGroup 继承 Engine 的 Run()ServeHTTP 等方法是没有意义的。

type RouterGroup struct {
	prefix      string // 支持叠加
	middlewares []HandlerFunc
	router      *router
}

type Engine struct {
	*RouterGroup
}

func New() *Engine {
	engine := &Engine{}
	engine.RouterGroup = &RouterGroup{router: newRouter()}
	return engine
}

func (group *RouterGroup) Group(prefix string) *RouterGroup {
	newGroup := &RouterGroup{
		prefix: group.prefix + prefix,
		router: group.router,
	}
	return newGroup
}

感觉组合关系这样子好像比较容易理解, RouterGroup继承了router的路由功能, Engine继承了RouterGroup的分组功能, 同时还有其他的Run, ServeHTTP等接口功能

@dont-see-big-shark
Copy link

groups暂时没有作用,是否是后面使用?

@How-invin
Copy link

看文章学一半,看评论再学一半/xk

@banzhihang
Copy link

Engine 和 RouterGroup 是否属于循环嵌套了呢

@sunanzhi
Copy link

如果只是用前缀当分组的话会不会适用场景不高?我试了下如果是这个场景

v1.Group("v1")
v1.Use(gee.Logger())
v1.GET("/run", func(c *gee.Context) {
		c.Json(http.StatusOK,  student{})
	})
r.GET("/v1/say", func(c *gee.Context) {
	c.Json(http.StatusOK, student{})
})

那么 v1/say也会执行中间件的代码
所以将group设置的时候分开一下,用key去区分会不会适用场景多点?可以明确同prefix的情况下不同后缀也可以执行不同的middleware

// 分组配置
type GroupConfig struct {
	Prefix string // 前缀
	Key    string // 分组key
}

刚初学,问题有点多,见谅。

@Licccccc
Copy link

type (
RouterGroup struct {
prefix string
middlewares []HandlerFunc // support middleware
parent *RouterGroup // support nesting
engine *Engine // all groups share a Engine instance
}

Engine struct {
	*RouterGroup
	router *router
	groups []*RouterGroup // store all groups
}

)
go小白,这个type()是什么用法。。。望指点

@Licccccc
Copy link

  • -!明白了,忽略

@geektutu
Copy link
Owner Author

@sunanzhi 这个就看如何去设计了,前缀区分一般是比较好的方式。比如

对一个博客系统来说,/auth/user 下的走授权中间件,/post/<id>.html,就不走授权,走统计中间件。/api/ 开头的,是对外提供的公共接口,诸如此类。是比较符合 URL 设计的习惯的。

特别是 Restful API,以资源为中心的 URL 设计,通过前缀做不同业务的区分更为明显,不同前缀代表不同类型的资源。

@fishjar
Copy link

fishjar commented Nov 11, 2020

请教一下:

1、能不能在路由的前缀树节点中直接记录分组以及中间件函数呢?node中增加一个是否分组的标记,以及中间件列表。省去EngineRouterGroup相互引用

2、另外,我觉得路由的handler是否也可以直接挂载到路由节点中呢?

大概这样:

// router.go
type router struct {
	root    *node
}
// trie.go
type node struct {
	pattern     string
	part        string
	children    []*node
	isWild      bool
	isGroup     bool
	middlewares []HandlerFunc
	handlers    map[string]HandlerFunc
}

@TuringBecky
Copy link

@geektutu
@201732110125 parent 之前设计是用来拼接 prefix 的,每个 group 只记录自己的部分,最后通过 parent 层层回溯拼接。不过后来改用 group.prefix + prefix 的方式 group 初始化时已经拼接了完整的 prefix,所以不需要 parent 了,可以删除。

如果去掉parent,在一个group中再创建一个group怎么办?

@xlban163
Copy link

@How-invin
看文章学一半,看评论再学一半/xk

没错没错

@geektutu
Copy link
Owner Author

@fishjar 你的想法是没问题的,当前的这种实现,前缀树解耦得比较好,功能比较单一,容易替换为其他的前缀树实现。Gin 这部分一开始也是利用了第三方实现,后来替换为自己的。

@geektutu
Copy link
Owner Author

@echomuof 再创建子 group 的话,用当前 group 的 prefix 拼接应该就 OK 。

@geektutu
Copy link
Owner Author

@xlban163 哈哈,也感谢你的评论。

@feixintianxia
Copy link

@geektutu
@sunanzhi 这个就看如何去设计了,前缀区分一般是比较好的方式。比如

对一个博客系统来说,/auth/user 下的走授权中间件,/post/<id>.html,就不走授权,走统计中间件。/api/ 开头的,是对外提供的公共接口,诸如此类。是比较符合 URL 设计的习惯的。

特别是 Restful API,以资源为中心的 URL 设计,通过前缀做不同业务的区分更为明显,不同前缀代表不同类型的资源。
同学, 可以这样理解,
/auth 或 /user 是和使用者操作相关,服务器需要判断该使用者是否授权。
/post/.html 是网页自身需要统计一些信息触发的,不需要授权。

@YellowPure
Copy link

这里RouterGroup和Engine是循环引用吗?
在写的时候犯了个错误

把GET函数中的group.addRoute("GET", pattern, handler)
写成了group.engine.addRoute("GET", pattern, handler)

导致生成的prefix是空字符串,请问这是为什么呢?理论上group指针的prefix应该是有值的,小白求教

@LAShZ
Copy link

LAShZ commented Feb 6, 2021

@YellowPure
这里RouterGroup和Engine是循环引用吗?
在写的时候犯了个错误

把GET函数中的group.addRoute("GET", pattern, handler)
写成了group.engine.addRoute("GET", pattern, handler)

导致生成的prefix是空字符串,请问这是为什么呢?理论上group指针的prefix应该是有值的,小白求教

调用group.engine.addRoute时,使用的是RouterGroup里面的engine结构从RouterGroup继承的addRoute方法,也就是说addRoute的作用对象是engine里面的RouteGroup指针指向的group变量。
观察函数gee.Group可以发现,当创建一个新的RouterGroup变量时,该变量内的engine结构实际上等于上一个group内的engine,而上一个engine内的RouterGroup指针指向的的group变量的prefix并没有加上当前Group函数传入的prefix,所以当你调用group.engine.addRoute时,得到的prefix实际上是调用gee.Group函数得到新的group之前的prefix。在给出的demo里,自然v1.engine.RouterGroup.prefix == ""

@YellowPure
Copy link

YellowPure commented Feb 18, 2021 via email

@huchiZhang
Copy link

有个问题,请问engine的结构体中为什么要包含RouterGroup,是做什么用的?

@chanLi-s
Copy link

@rictt
小白请教:在main函数中通过gee.New()返回的实例是engine类型的,接着r.GET(),这时候r是engine类型,是怎么走到func (group *RouterGroup) GET这个方法中的

// main.go
func main() {
    r := gee.New()
    r.GET('/index', func() {})
}

// gee.go
// r.GET('/index') 为什么会走到这里呢
// 这里的接收者类型明明是RouterGroup
func (group *RouterGroup) GET(path string, handler HandlerFunc) {
	fmt.Println("I'm in group GET, path is ", path)
	pattern := group.prefix + path
	group.addRoute("GET", pattern, handler)
}

@chanLi-s
Copy link

因为你New出来的实例中包含Group对象,在调用engine下的group也就会执行到这个get方法

@galaxyzen
Copy link

感觉可以把Engine和RouterGroup合并为一个类型,比如叫做"Dispatcher"之类的

type Dispatcher struct {
	basePath string
	router *router
	middlewares []HandlerFunc
}

@jiadamao
Copy link

@banzhihang
Engine 和 RouterGroup 是否属于循环嵌套了呢

我也想知道这个问题

@karmanluo
Copy link

curl "http://localhost:9999/v2/hello/geektutu"
hello geektutu, you're at /hello/geektutu

结果应该是:
hello geektutu, you're at /v2/hello/geektutu

@zmf173417897
Copy link

@jiadamao

@banzhihang
Engine 和 RouterGroup 是否属于循环嵌套了呢

我也想知道这个问题

我也想知道

@zmf173417897
Copy link

@jiadamao

@banzhihang
Engine 和 RouterGroup 是否属于循环嵌套了呢

我也想知道这个问题

不知道算不算嵌套,Engine算是根Group,里面包含一个groups队列,包含多个分组,每个group里面相当于有一个engine的指针

@DurantVivado
Copy link

@zmf173417897

@jiadamao

@banzhihang
Engine 和 RouterGroup 是否属于循环嵌套了呢

我也想知道这个问题

不知道算不算嵌套,Engine算是根Group,里面包含一个groups队列,包含多个分组,每个group里面相当于有一个engine的指针

我猜你应该想说子类继承父类,子类继承子类这种模式。但实际上Engine本身是指针,它本身继承了RouterGroup, RouterGroup右包含一个engine成员,你可以理解为双向链表。

@DurantVivado
Copy link

v1 := r.Group("/v1")
	{
		v1.GET("/", func(c *gee.Context) {
			c.HTML(http.StatusOK, "<h1>Hello Gee</h1>")
		})

		v1.GET("/hello", func(c *gee.Context) {
			// expect /hello?name=geektutu
			c.String(http.StatusOK, "hello %s, you're at %s\n", c.Query("name"), c.Path)
		})
	}

这里的大花括号是出于可读性设计的,与Group无关。

@AnthonyYangU
Copy link

@chanLi-s

@rictt
小白请教:在main函数中通过gee.New()返回的实例是engine类型的,接着r.GET(),这时候r是engine类型,是怎么走到func (group *RouterGroup) GET这个方法中的

// main.go
func main() {
    r := gee.New()
    r.GET('/index', func() {})
}

// gee.go
// r.GET('/index') 为什么会走到这里呢
// 这里的接收者类型明明是RouterGroup
func (group *RouterGroup) GET(path string, handler HandlerFunc) {
	fmt.Println("I'm in group GET, path is ", path)
	pattern := group.prefix + path
	group.addRoute("GET", pattern, handler)
}

因为Engine继承了RouterGroup

@liujing-siyang
Copy link

RouterGroup实现了Group方法,而Engine 拥有 RouterGroup,那RouterGroup不论保不保存Engine的指针,RouterGroup和Engine都可以添加路由吧

@fatFire
Copy link

fatFire commented Feb 11, 2022

@SourceLink

@geektutu
@xenv Go语言的嵌套在其他语言中类似于继承,子类必然是比父类有更多的成员变量和方法。RouterGroup 仅仅是负责分组路由,Engine 除了分组路由外,还有很多其他的功能。RouterGroup 继承 Engine 的 Run()ServeHTTP 等方法是没有意义的。

type RouterGroup struct {
	prefix      string // 支持叠加
	middlewares []HandlerFunc
	router      *router
}

type Engine struct {
	*RouterGroup
}

func New() *Engine {
	engine := &Engine{}
	engine.RouterGroup = &RouterGroup{router: newRouter()}
	return engine
}

func (group *RouterGroup) Group(prefix string) *RouterGroup {
	newGroup := &RouterGroup{
		prefix: group.prefix + prefix,
		router: group.router,
	}
	return newGroup
}

感觉组合关系这样子好像比较容易理解, RouterGroup继承了router的路由功能, Engine继承了RouterGroup的分组功能, 同时还有其他的Run, ServeHTTP等接口功能

感觉你这样写更好理解一些

@Allsochen
Copy link

all groups share a Engine instance应该修改为all groups share an Engine instance

@heql0668
Copy link

heql0668 commented Apr 7, 2022

首先,非常感谢大神写的教程,受益匪浅,学习到了很多,非常感谢,已打赏。通过阅读此章节,我发现关于分组的这块是比较绕的,有点难理解,在此我提出一些我觉得更好的优化建议

  • 首先明确各个模块的功能职责
    • router职责:
      • 注册路由
      • 路由查询
      • 路由请求到相应已注册的处理器进行处理
    • RouterGroup职责:
      • 对路由进行分组(对注册的路由路径添加分组前缀, 类似面向对象编程的方法重写,给路由的路径添加完前缀,再调用父类的方法[注册路由方法])
      • 基于组的粒度对路由进行控制
    • Engine职责
      • router,RouterGroup可以看做是web底层的通用的基础功能, 而Engine就是这些功能的使用者
      • 同时Engine还有它自己的职责
        • 运行服务
        • 接收客户端的请求

基于以上的职责模块划分,这里贴出我觉得可以改进的模块

group.go

package gee

import (
	"log"
)

type RouterGroup struct {
	*router
	prefix      string
	parent      *RouterGroup
	middlewares []*HandlerFunc
}

// 每一个路由的路径肯定是有根的, 那么可以创建一个默认的根路由分组,新加分组必定是基于根路由分组进行新增的
func newRootGroup() *RouterGroup {
	return &RouterGroup{
		prefix: "",
		router: NewRouter(),
	}
}

// 所有分组已经路由相关的管理操作的代码,不应该放置到gee.go

func (group *RouterGroup) Group(prefix string) *RouterGroup {
	prefix = group.prefix + prefix
	newGroup := &RouterGroup{
		prefix: prefix,
		parent: group,
		router: group.router,
	}
	return newGroup
}

// 这里类似面向对象编程一样重写router.addRoute
func (group *RouterGroup) addRoute(method string, pattern string, handler HandlerFunc) {
	pattern = group.prefix + pattern
	log.Printf("Route %4s - %s\n", method, pattern)
  // 调用父类的addRoute
	group.router.addRoute(method, pattern, handler)
}

func (group *RouterGroup) GET(pattern string, handler HandlerFunc) {
	group.addRouter("GET", pattern, handler)
}

func (group *RouterGroup) POST(pattern string, handler HandlerFunc) {
	group.addRouter("POST", pattern, handler)
}

gee.go

package gee

import "net/http"

type Engine struct {
	*RouterGroup // 可以看做是继承底层模块所拥有的能力
	groups []*RouterGroup
}

func New() *Engine {
	group := newRootGroup()
	engine := &Engine{
		RouterGroup: group,
		groups:      []*RouterGroup{group},
	}
	return engine
}

// engine想知道自己创建了多少个分组, 这里就应该由它自己来进行统计, 这是engine的职责, 而不是RouterGroup的
func (e *Engine) Group(prefix string) *RouterGroup {
	group := e.RouterGroup.Group(prefix)
	e.groups = append(e.groups, group)
	return group
}

func (e *Engine) Run(addr string) (err error) {
	return http.ListenAndServe(addr, e)
}

func (e *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	c := newContext(w, req)
	e.handle(c) // 使用底层模块提供的能力
}

@vfmh
Copy link

vfmh commented Apr 12, 2022

RouterGroup里面为什么需要*Engine呀,感觉没啥用呀

@sphierex
Copy link

sphierex commented May 6, 2022

@bjpwg
博主,好像:Engine 的 groups 没有用上?

GroupRouter 通过 engine 实例,将自身的路由注册到 handlers 里。

func (group *RouterGroup) addRoute(method string, comp string, handler HandlerFunc) {
	pattern := group.prefix + comp
	log.Printf("Route %4s - %s", method, pattern)
    // 这里
	group.engine.router.addRoute(method, pattern, handler)
}

@abcdhope
Copy link

func (group *RouterGroup) Group(prefix string) *RouterGroup {
	engine := group.engine
	newGroup := &RouterGroup{
		prefix: group.prefix + prefix,
		parent: group,
		engine: engine,
	}
	engine.groups = append(engine.groups, newGroup)
	return newGroup
}

中的engine := group.engine是否可以去除而直接使用group.engine呢?

@loveyu233
Copy link

实现嵌套路由

func (r *RouterGroup) AddRouter(method, comp string, handler HandlerFunc) {
	var prefixs []string
	this := r
	for true {
		if this.prefix == "" {
			break
		}
		prefixs = append(prefixs, this.prefix)
		this = this.parent
	}
	prefix := ""
	for i := len(prefixs) - 1; i >= 0; i-- {
		prefix += prefixs[i]
	}
	pattern := prefix + comp
	fmt.Println(pattern)
	r.engine.router.addRouter(method, pattern, handler)
}

@ueueQ
Copy link

ueueQ commented Jun 29, 2022

吸收各位大佬的精华,写了一个自己的。
gee.go

type Engine struct {
	rootRouterGroup *RouterGroup //最顶级路由
	router          *router      //本组方法匹配
}

func New() *Engine {
	e := &Engine{router: newRouter()}
	e.rootRouterGroup = newRouterGroup("", e.router)
	return e
}

group.go

type RouterGroup struct {
	prefix         string                  //本组的路由前缀
	router         *router                 //接收上级router,均使用顶级router
	middleware     []HandlerFunc           //本组中间件
	subRouterGroup map[string]*RouterGroup //下级路由组
}

func newRouterGroup(prefix string, supRouter *router) *RouterGroup {
	return &RouterGroup{
		prefix:         prefix,
		router:         supRouter,
		subRouterGroup: make(map[string]*RouterGroup), //组前缀为键;组指针为值
	}
}

func (rg *RouterGroup) AddGroup(prefix string) *RouterGroup { //添加路由分组
	if group, ok := rg.subRouterGroup[prefix]; ok {
		return group
	}
	subGroup := newRouterGroup(rg.prefix+prefix, rg.router)
	rg.subRouterGroup[prefix] = subGroup
	return subGroup
}

func (rg *RouterGroup) addRouter(method, subPattern string, userHandler HandlerFunc) {
	pattern := rg.prefix + subPattern
	rg.router.addRouter(method, pattern, userHandler)
}

func (rg *RouterGroup) Get(subPattern string, userHandler HandlerFunc) {
	rg.addRouter("GET", subPattern, userHandler)
}

func (rg *RouterGroup) Post(subPattern string, userHandler HandlerFunc) {
	rg.addRouter("POST", subPattern, userHandler)
}

@bigrainking
Copy link

@vfmh
RouterGroup里面为什么需要*Engine呀,感觉没啥用呀

引用了之后RouterGroup才可以使用Engine的功能属性呀,比如 group.engine.router.addRouter()

@bigrainking
Copy link

@huchiZhang
有个问题,请问engine的结构体中为什么要包含RouterGroup,是做什么用的?

  1. engine如果理解为全局所有路由分组的父亲分组,那么engine本身就是一个路由分组,他就应该具备路由分组的所有功能,因此engine需要内嵌一个*RouterGroup以便拥有路由分组所有特性
    2.对于 (group *RouterGrou) Group() : 让engine 或者是 RouterGroup都可以创建路由分组
    在mian.go中,有如下调用,
    r := gee.New() //r = (*Engine)
    r.Group() //(*Engine).Group()

@hdhgpy
Copy link

hdhgpy commented May 13, 2023

这节少说很多东西,如果不去看源码,下节肯定一脸懵逼

@CowToTheSky
Copy link

配合chatgpt使用还是比较好的,直接把新加的代码块放进去问,会一句一句的回答

@lzb200244
Copy link

这一小节个人认为,添加组实际上是在group的基础上再到组prefix,实际上是在进行插入前缀树,可以根据当前找到gropu.prefix然后在进行拼接,进行查找是否存在该请求路径。但是在这一小块有点问题groups []*RouterGroup 这块是组中组吗?各位佬帮我解答一下吗?

@Fencent
Copy link

Fencent commented Oct 16, 2023

萌新提问两点

问题一

func New() *Engine {
	engine := &Engine{router: newRouter()}
	engine.RouterGroup = &RouterGroup{engine: engine}
	//engine.groups = []*RouterGroup{engine.RouterGroup}
	engine.groups = make([]*RouterGroup, 0)
	return engine
}

上面代码中第四行(注释行)是否可以改写成第五行,搞不明白初始化时在groups中加入RouterGroup的意义。

问题二

既然Engine继承了RouterGroup的方法,是否可以删除Engine自身的GET与POST方法,因为即使在不使用分组的情况下效果也是一样的

@coderZoe
Copy link

coderZoe commented Jul 5, 2024

@ueueQ
吸收各位大佬的精华,写了一个自己的。
gee.go

type Engine struct {
	rootRouterGroup *RouterGroup //最顶级路由
	router          *router      //本组方法匹配
}

func New() *Engine {
	e := &Engine{router: newRouter()}
	e.rootRouterGroup = newRouterGroup("", e.router)
	return e
}

group.go

type RouterGroup struct {
	prefix         string                  //本组的路由前缀
	router         *router                 //接收上级router,均使用顶级router
	middleware     []HandlerFunc           //本组中间件
	subRouterGroup map[string]*RouterGroup //下级路由组
}

func newRouterGroup(prefix string, supRouter *router) *RouterGroup {
	return &RouterGroup{
		prefix:         prefix,
		router:         supRouter,
		subRouterGroup: make(map[string]*RouterGroup), //组前缀为键;组指针为值
	}
}

func (rg *RouterGroup) AddGroup(prefix string) *RouterGroup { //添加路由分组
	if group, ok := rg.subRouterGroup[prefix]; ok {
		return group
	}
	subGroup := newRouterGroup(rg.prefix+prefix, rg.router)
	rg.subRouterGroup[prefix] = subGroup
	return subGroup
}

func (rg *RouterGroup) addRouter(method, subPattern string, userHandler HandlerFunc) {
	pattern := rg.prefix + subPattern
	rg.router.addRouter(method, pattern, userHandler)
}

func (rg *RouterGroup) Get(subPattern string, userHandler HandlerFunc) {
	rg.addRouter("GET", subPattern, userHandler)
}

func (rg *RouterGroup) Post(subPattern string, userHandler HandlerFunc) {
	rg.addRouter("POST", subPattern, userHandler)
}

我觉得你这个写的最好

@blkcor
Copy link

blkcor commented Jul 7, 2024

@Fencent
萌新提问两点

问题一

func New() *Engine {
	engine := &Engine{router: newRouter()}
	engine.RouterGroup = &RouterGroup{engine: engine}
	//engine.groups = []*RouterGroup{engine.RouterGroup}
	engine.groups = make([]*RouterGroup, 0)
	return engine
}

上面代码中第四行(注释行)是否可以改写成第五行,搞不明白初始化时在groups中加入RouterGroup的意义。

问题二

既然Engine继承了RouterGroup的方法,是否可以删除Engine自身的GET与POST方法,因为即使在不使用分组的情况下效果也是一样的

问题一:
区别就是有没有把root RouterGroup放进去,如果root RouterGroup的前缀不为/而是/foo/bar这样子,对于本章没什么影响,因为没有用到engine.groups,应该和后面的middleware一张对group添加中间件有影响

问题二:
从结果出发是可以的 但是保存分组本身的信息十分消耗资源,最好是把raw router和group router分开才是最好的选择,gin本身也是这样做的

@weedsx
Copy link

weedsx commented Aug 8, 2024

@Fencent
萌新提问两点

问题一

func New() *Engine {
	engine := &Engine{router: newRouter()}
	engine.RouterGroup = &RouterGroup{engine: engine}
	//engine.groups = []*RouterGroup{engine.RouterGroup}
	engine.groups = make([]*RouterGroup, 0)
	return engine
}

上面代码中第四行(注释行)是否可以改写成第五行,搞不明白初始化时在groups中加入RouterGroup的意义。

问题二

既然Engine继承了RouterGroup的方法,是否可以删除Engine自身的GET与POST方法,因为即使在不使用分组的情况下效果也是一样的

对于问题二,不需要删除Engine自身的GET与POST方法,虽然逻辑上是继承,但通过Engine和RouterGroup来创建路由、回调本身就是两个行为,而且这两个行为最后都是将路由注册到路由前缀树上

@HeliumzzZ
Copy link

@SourceLink

@geektutu
@xenv Go语言的嵌套在其他语言中类似于继承,子类必然是比父类有更多的成员变量和方法。RouterGroup 仅仅是负责分组路由,Engine 除了分组路由外,还有很多其他的功能。RouterGroup 继承 Engine 的 Run()ServeHTTP 等方法是没有意义的。

type RouterGroup struct {
	prefix      string // 支持叠加
	middlewares []HandlerFunc
	router      *router
}

type Engine struct {
	*RouterGroup
}

func New() *Engine {
	engine := &Engine{}
	engine.RouterGroup = &RouterGroup{router: newRouter()}
	return engine
}

func (group *RouterGroup) Group(prefix string) *RouterGroup {
	newGroup := &RouterGroup{
		prefix: group.prefix + prefix,
		router: group.router,
	}
	return newGroup
}

感觉组合关系这样子好像比较容易理解, RouterGroup继承了router的路由功能, Engine继承了RouterGroup的分组功能, 同时还有其他的Run, ServeHTTP等接口功能

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests