关于interface和struct

来源:16-2 简单调度器

nitros

2020-03-16

老师好

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

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

关于简单调度器

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

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回答

ccmouse

2020-03-18

你这个相当于把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
4
nitros
回复
ccmouse
感谢老师的再次回复。 对于"使用"这里的概念隐约间有了一些理解。完全理解我估计还要花些时间吧。 那这里对于Scheduler的使用就是来源于 main函数里 e := engine.ConcurrentEngine{ Scheduler: &scheduler.QueuedScheduler{}, WorkerCount: 100, ItemChan: itemChan, RequestProcessor: engine.Worker, } 这里scheduler类型的定义上对吧? 其实就是赋予scheduler了如下这样的定义 var s scheduler = ConcurrentEngine{Scheduler: &scheduler.QueuedScheduler{},} 不知道这样理解对不对。总觉得这里好绕
2020-03-18
共4条回复

ccmouse

2020-03-18

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

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

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

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

0
0

Google资深工程师深度讲解Go语言 由浅入深掌握Go语言

语法+分布式爬虫实战 为转型工程师量身打造

5995 学习 · 1908 问题

查看课程