请稍等 ...
×

采纳答案成功!

向帮助你的同学说点啥吧!感谢那些助人为乐的人

关于interface和struct

老师好

在简单调度器这里,有一些不太清楚的地方希望老师能给解惑一下。

主要是关于结构体和接口这里的设计理念问题。

关于简单调度器

首先我们先做了一个并发版的引擎

type ConcurrentEngine struct {
}

之后我们围绕并发版引擎做了一些方法。比如submit

这时候就有了

type Scheduler interface {
  Submit()
}

好了问题来了。

这里老师给的代码是

e.Scheduler.Submit(r)

然后在
​ConcurrentEngine 肚子里放了调度器

我想问下
通过代码

e.Submit(r)

然后我再写个关于并发引擎的方法来实现这个interface不可以吗?

func (c *ConcurrentEngine)Submit(request Request) {}

老师那样写的好处是什么呢。那里的设计理念和中心思想是什么呢?

还请老师给解惑一下

另外附上自己更改的一部分理解后的代码。麻烦老师给指点下哪里错误了

package engine

import (
	"log"
)

type ConcurrentEngine struct {
	WorkerCount int
	requestChan chan Request
}

type Scheduler interface {
	Submit(Request)
}

func (*ConcurrentEngine) Submit(request Request, in chan Request) {
	go func() {
		in <- request
	}()
}

func (c *ConcurrentEngine) Run(Seeds ...Request) {
	out := make(chan ParserResult)
	c.requestChan = make(chan Request)
	for _, req := range Seeds {
		c.Submit(req, c.requestChan)
	}

	for i := 0; c.WorkerCount > i; i++ {
		c.createWorker(c.requestChan, out)
	}

	for {
		result := <-out

		for _, re := range result.Request {
			c.Submit(re, c.requestChan)
		}

		for _, item := range result.Items {
			log.Printf("got item : %s\n", item)
		}
	}
}

func (*ConcurrentEngine) createWorker(in chan Request, out chan ParserResult) {
	go func() {
		for {
			req := <-in
			result, err := Worker(req)
			if err != nil {
				continue
			}
			out <- result
		}
	}()
}

正在回答

2回答

你这个相当于把simple scheduler的部分整合进了这个ConcurrentEngine。可以看一下simple scheduler本身没几行代码。https://git.imooc.com/coding-180/coding-180/src/master/crawler/scheduler/simple.go 

首先的问题是scheduler要不要整合进来,还是像我课上那样作为一个单独的模块。我的建议是做成单独的模块。本身它的任务很明确,就是将request分发给worker,没有任何业务逻辑,但是可以做的很复杂。

有两个模块的前提下,我们才需要使用接口的概念。让两个scheduler(https://git.imooc.com/coding-180/coding-180/src/master/crawler/scheduler/simple.go 和 https://git.imooc.com/coding-180/coding-180/src/master/crawler/scheduler/queued.go   )都去实现scheduler接口。那么我们这里在concurrentEngine里面就可以把任务交给scheduler去分发,具体就体现在你这行疑问的代码:

e.Scheduler.Submit(r)

你的修改是把两个模块合成一个模块。当然也能运行,但这样的情况下就不需要存在接口了。Submit函数本身可以留着,但是

type Scheduler interface {
 Submit()
}

就不需要了。我们没有这个接口,也可以调用Submit函数,并不是说一定要有接口或者一定不需要。可以考虑一下你这里的Scheduler接口并没有被用到,只是被实现了,但是没人用。在c.Submit(req, c.requestChan)是直接调用函数,没有通过接口。

接口的作用是一个模块(比如ConcurrentEngine)要调用另一个模块(比如SimpleScheduler)的时候,使用者(这里指ConcurrentEngine)说,我需要跟我合作的模块满足Scheduler接口。

1 回复 有任何疑惑可以回复我~
  • 提问者 nitros #1
    非常感谢!
    回复 有任何疑惑可以回复我~ 2020-03-18 16:01:36
  • 提问者 nitros #2
    感谢老师的耐心回复。
    我看了下老师的讲解,由于接口这里不是很顺所以问题可能比较多,希望老师能理解。
    问题如下
    这里说到了使用者和实现者。
    在老师源代码里,simpleScheduler和queuedScheduler分别是两个调度器(结构体),这两个调度器实现了scheduler这个interface里的所有功能。
    然后ConcurrentEngine其实是scheduler这个interface的使用者。是这么理解的吗
    
    那么我的代码里,为什么说scheduler没被使用呢?concurrentEngine也实现了他的方法,但是没有被使用?这里的使用要如何去理解呢
    回复 有任何疑惑可以回复我~ 2020-03-18 16:05:57
  • ccmouse 回复 提问者 nitros #3
    你的问题很好,都问在点子上。这个“使用”怎么去理解。
    概念上,接口在两个或多个模块之间作用,才谈得上有“使用”自己调自己,这个不算“使用”。你这里的Submit只有自己对自己的调用,做成private函数都行,也就是小写的submit函数。
    代码上,必须有一个变量,他被定义成Scheduler,而Scheduler是一个interface,我们又调用了这个变量的.Submit方法,这才叫“使用”。
    var e ConcurrentEngine
    e.Submit(r) // 虽然你这里ConcurrentEngine实现了Scheduler接口,但这个不叫“使用”Scheduler接口,你“使用”的是ConcurrentEngine
    而
    var s Scheduler = e
    s.Submit() // 这才叫使用Scheduler接口。
    
    作这个区分的意义是s.Submit()的时候,这个s可以是任何东西,只要有Submit()方法就行,不局限于ConcurrentEngine。
    回复 有任何疑惑可以回复我~ 2020-03-18 16:27:01
ccmouse 2020-03-18 15:38:50

我又看了下问题,问题在于,

应该是:因为我要分成两个模块,所以要定义一个scheduler接口,

而不是:因为有一个scheduler接口,所以要分成两个模块。

分两个模块的原因是scheduler的任务很明确,就是将request分发给worker,没有任何业务逻辑,但是可以做的很复杂。本身我们也的确实现了两个scheduler,它们都能通过Scheduler接口和ConcurrentEngine进行工作。

0 回复 有任何疑惑可以回复我~
问题已解决,确定采纳
还有疑问,暂不采纳
意见反馈 帮助中心 APP下载
官方微信