首先要做的是,先为组件通用功能定义一个内部接口,这里把它叫做组件的内部基础接口。内部基础接口及其实现类型存放在了代码包 gopcp.v2/chapter6/webcrawler/module/stub 中,代码包可以在我的网盘中下载(链接:https://pan.baidu.com/s/1yzWHnK1t2jLDIcTPFMLPCA 提取码:slm5 或 本地下载)。
该接口内嵌了之前讲过的 Module 接口,其声明如下:
//组件的内部基础接口的类型
type ModuleInternal interface {
module.Module
//把调用计数增 1
IncrCalledCount()
//把接受计数增1
IncrAcceptedCount()
//把成功完成计数增 1
IncrCompletedCount()
//把实时处理数增 1
IncrHandlingNumber()
//把实时处理数减 1
DecrHandlingNumber()
//用于清空所有计数
Clear()
}
Module 接口中声明的更多的是获取内部状态的方法,比如:获取组件 ID、组件地址、各种计数值,等等。而在 ModuleInternal 接口中,添加的方法都是改变内部状态的方法。
由于通常情况下外部不应该宜接改变组件的内部状态,所以该接口的名字才以 "Internal" 为后缀,以起到提示的作用。并且,在 gopcp.v2/chapter6/webcrawler/module 包中公开的程序实体并没有涉及该接口。
ModuleInternal 接口及其实现类型只是为了方便自行编写组件的人而准备的。在编写组件时也用到了它们。
ModuleInternal 接口是 Module 接口的扩展,前者的实现类型自然也是后者的实现类型。这个实现类型命名为 myModule,它的基本结构如下:
//组件内部基曲接口的实现类型
type myModule struct {
//组件ID
mid module.MID
//组件的网络地址
addr string
//组件评分
score uint64
//评分计算器
scoreCalculator module.CalculateScore
//调用计数
calledCount uint64
//接受计数
acceptedCount uint64
//成功完成计数
completedCount uint64
//实时处理数
handlingNumber uint64
}
这些字段都是理所应当存在的,它们分别与 Module 接口(以及 ModuleInternal 口)中声明的方法有直接的对应关系。按照惯例, NewModuleInternal 用于新建一个 ModuleInternal 类型的实例,它的声明如下。
//创建一个组件内部基础类型的实例
func NewModuleInternal(
mid module.MID,
scoreCalculator module.CalculateScore) (ModuleInternal, error) {
parts, err := module.SplitMID(mid)
if err != nil {
return nil, errors.NewIllegalParameterError(
fmt.Sprintf("illegal ID %q: %s", mid, err))
}
return &myModule{
mid: mid,
addr: parts[2],
scoreCalculator: scoreCalculator
}, nil
}
myModule 类型中的字段有几个是需要显式初始化的,包括:组件 ID、组件的网络地址(下面简称组件地址)和组件评分计算器。参数 mid 提供了组件 ID,同时也提供了组件地址。因为组件 ID 中可以包含组件地址。
如果组件地址为空,就说明该组件与网络爬虫程序同处在一个进程中。这时的 addr 字段自然就是 ""。module 包的 SplitMID 函数用于分离出组件 ID 的各个部分,并在组件 ID 不符合规范时报错,它是 module 包中众多工具类函数中的一个。
与之相对应,module 包中还有一个 GenMID 函数,用它可以生成组件 ID。调用 GenMID 函数时,需要给定一个序列号。大家可以通过调用 module 包中的 NewSNGenertor 函数创建出一个序列号生成器。强烈建议把序列号生成器的实例赋给一个全局变量。
组件评分计算器理应由外部提供,并且一般会为同一类组件实例设置同一种组件评分计算器,而且一旦设置就不允许更改。所以,即使是 ModuleInternal 接口也没有提供改变它的方法。
再强调一下,NewIllegalParameterError 是 gopcp.v2/chapter6/webcrawler/errors 包中的函数。该包中还有 NewCrawlerError 和 NewCrawlerErrorBy 函数,用于生成爬虫程序运作过程中抛出的错误值。
有了上述的那些字段,实现 ModuleInternal 接口的方法就相当简单了,唯一要注意的就是充分利用原子操作保证它们的并发安全。这里就不展示了。或许大家可以试着编写出来,然后对比看看。