【服务计算】六 改进或扩展 RxGo 包

简介

ReactiveX是Reactive Extensions的缩写,一般简写为Rx,最初是LINQ的一个扩展,由微软的架构师Erik Meijer领导的团队开发,在2012年11月开源,Rx是一个编程模型,目标是提供一致的编程接口,帮助开发者更方便的处理异步数据流,Rx库支持.NET、JavaScript和C++,Rx近几年越来越流行了,现在已经支持几乎全部的流行编程语言了,Rx的大部分语言库由ReactiveX这个组织负责维护,比较流行的有RxJava/RxJS/Rx.NET,社区网站是 reactivex.io中文文档

为什么要重写

Go 语言的 RxGo 看上去就像 go 入门不久的人写的,很怪异。 但 RxJava 库写的很好。

pmlpml/RxGo 模仿 Java 版写了 Go 版本新实现,已基本实现了 Creating Observables 和 Transforming Observables 两类算子。

课程任务

阅读 ReactiveX 文档。请在 pmlpml/RxGo 基础上,

  1. 修改、改进它的实现
  2. 或添加一组新的操作,如 filtering

该库的基本组成:

rxgo.go给出了基础类型、抽象定义、框架实现、Debug工具等

generators.go 给出了 sourceOperater 的通用实现和具体函数实现

transforms.go 给出了 transOperater 的通用实现和具体函数实现

实验要求

  • 在 Gitee 或 Github 提交程序,并在 specification.md 文件中描述设计说明,单元或集成测试结果,功能测试结果。
  • 如果你写了 关于 golang 或其他与课程相关的博客,请在作业提交时填写 url

操作系统

按照实验要求,结合自己的电脑设备与系统等条件,使用VirtualBox下Ubuntu 20.04系统完成实验。虚拟机相关设置与上一次实验相同。

环境准备

虚拟机下的实验环境与上一次实验的相同,不需要额外的配置。

开发实践

根据go的工作空间目录结构,由于现在考虑改进pmlpml的go语言的ReactiveX包,因此下文的工作目录默认在"$GOPATH/src/gitee.com/pmlpml/rxgo"下。而使用改进后pmlpml/rxgo包的包路径位于"$GOPATH/src/gitee.com/alphabstc/useRxGo"下(alphabstc为我的gitee和github的用户id)。

对pmlpml/RxGo包的了解和使用

下载包原始版本到本地

首先将老师编写的go语言的ReactiveX包下载到工作目录"$GOPATH/src/gitee.com/pmlpml/rxgo"下:
在这里插入图片描述
之后,可以看到工作路径下成功下载了pmlpml/rxgo包。
在这里插入图片描述
这样,就成功下载了pmlpml/RxGo包原始版本到本地。

可以阅读Readme.md和代码文件,了解该包的基本组成:

  • rxgo.go给出了基础类型、抽象定义、框架实现、Debug工具等
  • generators.go 给出了 sourceOperater 的通用实现和具体函数实现
  • transforms.go 给出了 transOperater 的通用实现和具体函数实现

之后根据Readme.md文档中的例子等内容,尝试使用RxGO包。

Hello World

学习一门语言,使用一个包,一般都是从"Hello World"入手。

该"Hello world"示例使用了generators.go中的Just方法。

根据文档中对Just方法的描述:

在这里插入图片描述
Just将单个数据转换为发射那个数据的Observable。
Just类似于From,但是From会将数组或Iterable的数据取出然后逐个发射,而Just只是简单的原样发射,将数组或Iterable当做单个数据。
注意:如果你传递null给Just,它会返回一个发射null值的Observable。不要误认为它会返回一个空Observable(完全不发射任何数据的Observable),如果需要空Observable你应该使用Empty操作符。
RxJava将这个操作符实现为just函数,它接受一至九个参数,返回一个按参数列表顺序发射这些数据的Observable。

在目录"$GOPATH/src/gitee.com/alphabstc/useRxGo"下新建helloworld.go文件,输入以下代码:

package main

import (
	"fmt"
	RxGo "github.com/pmlpml/rxgo"
)

func main() {
	RxGo.Just("Hello", "World", "!").Subscribe(func(x string) {
		fmt.Println(x)
	})
}

之后运行:
在这里插入图片描述

可以看见,其将Just函数的三个参数"Hello", “World”, "!"分别在一行中输出,符合要求。

数据流上链操作

RxGo中的数据流由一个源、零个或多个中间步骤组成,接下来是一个数据消费者或组合者步骤(其中该步骤负责以某种方式消费数据流)。

上面已经实践了简单单个操作的情况,现在考虑通过一个例子使用与实践数据流上的链操作。

在目录"$GOPATH/src/gitee.com/alphabstc/useRxGo"下新建chainedOperations.go文件,输入以下代码:

package main

import (
	"fmt"
	RxGo "github.com/pmlpml/rxgo"
)

func fibonacci(max int) func() (int, bool) {
	a, b := 0, 1
	return func() (r int, end bool) {
		r = a
		a, b = b, a+b
		if r > max {
			end = true
		}
		return
	}
}

func main() {
	RxGo.Start(fibonacci(10)).Map(func(x int) int {
		return 2*x
	}).Subscribe(func(x int) {
		fmt.Println(x)
	})
}

分析上面的代码,其定义了fibonacci函数,该函数为一个闭包,第i次调用会返回斐波那契数列中第i项的结果,并且在斐波那契数列中第i项的结果超过max时,将end设置为true。主函数通过调用Start(fibonacci(10))使得产生了斐波那契数列不超过10的前几项,然后通过Map(func(x int) int {return 2*x})将这些值乘以2,之后再用Subscribe(func(x int) {fmt.Println(x)})将上面Map的结果输出。

在这里插入图片描述
可以看见,输出了上面Map操作结果,由于采取println输出,因此每行输出一个数,符合要求。

可连接的observables

可连接的Observable类似于普通的Observable,但它在subscribed 时不开始发射item,而只在调用其connect()方法时才开始发出项。

数据管道有两个阶段:

  • 第一个阶段叫做定义。我们为管道定义源函数或运算符,这与为每个节点分配工作角色相同
  • 下一个阶段称为运行时。当任何observable调用Subscribe(…)时,包含该observable的管道将connect它的所有节点或指定一个或多个工作线程站在每个节点旁边。如果前置工作线程发出数据,则该工作进程将扮演之前定义阶段指定的角色,并将结果发送给下一个工作进程。

在目录"$GOPATH/src/gitee.com/alphabstc/useRxGo"下新建connect.go文件,输入以下代码:

package main

import (
	"fmt"
	RxGo "github.com/pmlpml/rxgo"
)

func main() {
    //define pipeline
	source := RxGo.Just("Hello", "World", "!")
	next := source.Map(func(s string) string {
		return s + " "
	})
    //run pipeline
	next.Subscribe(func(x string) {
		fmt.Print(x)
	})
	fmt.Println()
	source.Subscribe(func(x string) {
		fmt.Print(x)
	})
}

运行这份代码,会输出下面的结果:
在这里插入图片描述
可以看到,上面的程序展示了管道重启的功能,输出了两次"Hello World ! "

修改已有实现

先测试

先在rxgo包的目录下运行一次测试:
在这里插入图片描述
可以看到通过了测试,符合要求。

改写

现在尝试改进包的代码。

首先,可以去掉transforms.go中flatMapOperater的定义的多余的if !end 条件判断部分,将会产生相同结果的分支进行合并,得到如下的结果:

var flatMapOperater = transOperater{func(ctx context.Context, o *Observable, x reflect.Value, out chan interface{}) (end bool) {

	fv := reflect.ValueOf(o.flip)
	var params = []reflect.Value{x}
	//fmt.Println("x is ", x)
	rs, skip, stop, e := userFuncCall(fv, params)

	var item = rs[0].Interface().(*Observable)

	if stop {
		end = true
		return
	}
	if skip {
		return
	}
	if e != nil {
		end = o.sendToFlow(ctx, e, out)
		return
	}
	// send data
	if !end {
		if item != nil {
			// subscribe ro without any ObserveOn model
			ro := item
			for ; ro.next != nil; ro = ro.next {
			}
			ro.connect(ctx)

			ch := ro.outflow
			for x := range ch {
				end = o.sendToFlow(ctx, x, out)
				if end {
					return
				}
			}
		}
	}
	return
}}

对于transforms.go中的op,在其中创建的go程内部,一旦end变为了true之后就不会变为false。因此,可以将if end { continue }修改为if end { break }。使得一旦循环后面的语句都不会运行,就可以直接退出循环,不用反复进行判断,提高程序运行效率。

func (tsop transOperater) op(ctx context.Context, o *Observable) {
	// must hold defintion of flow resourcs here, such as chan etc., that is allocated when connected
	// this resurces may be changed when operation routine is running.
	in := o.pred.outflow
	out := o.outflow
	//fmt.Println(o.name, "operator in/out chan ", in, out)
	var wg sync.WaitGroup

	go func() {
		end := false
		for x := range in {
			if end {
				break
			}
			// can not pass a interface as parameter (pointer) to gorountion for it may change its value outside!
			xv := reflect.ValueOf(x)
			// send an error to stream if the flip not accept error
			if e, ok := x.(error); ok && !o.flip_accept_error {
				o.sendToFlow(ctx, e, out)
				continue
			}
			// scheduler
			switch threading := o.threading; threading {
			case ThreadingDefault:
				if tsop.opFunc(ctx, o, xv, out) {
					end = true
				}
			case ThreadingIO:
				fallthrough
			case ThreadingComputing:
				wg.Add(1)
				go func() {
					defer wg.Done()
					if tsop.opFunc(ctx, o, xv, out) {
						end = true
					}
				}()
			default:
			}
		}

		wg.Wait() //waiting all go-routines completed
		o.closeFlow(out)
	}()
}

单元测试和集成测试

进行上述修改后,再次测试如下:
在这里插入图片描述
依然可以正常通过单元测试和集成测试,符合要求。

功能测试

再次运行之前的使用示例代码,结果如下:

在这里插入图片描述
在这里插入图片描述
可见三份使用RxGo包的示例代码目前功能仍然正常,符合要求。

添加一组新的操作

现在考虑在包中添加一组新的操作,这里选择添加filtering组中的操作。

Filtering Observables有着如下的功能:

  • Debounce — only emit an item from an Observable if a particular timespan has passed without it emitting another item
  • Distinct — suppress duplicate items emitted by an Observable
  • ElementAt — emit only item n emitted by an Observable
  • Filter — emit only those items from an Observable that pass a predicate test
  • First — emit only the first item, or the first item that meets a condition, from an Observable
  • IgnoreElements — do not emit any items from an Observable but mirror its termination notification
  • Last — emit only the last item emitted by an Observable
  • Sample — emit the most recent item emitted by an Observable within periodic time intervals
  • Skip — suppress the first n items emitted by an Observable
  • SkipLast — suppress the last n items emitted by an Observable
  • Take — emit only the first n items emitted by an Observable
  • TakeLast — emit only the last n items emitted by an Observable

设计思路

首先学习老师pmlpml已经编写的代码。可以看到老师编写的代码包rxgo中,transforms.go实现了Transforming Observable Items中的各个操作。这些操作被使用时,会先通过Start()函数创建一个新的observable,并根据操作类型赋予其相应属性,这仅仅是设置了相应属性的初始值,并没有计算出这些操作相应的结果。直到这些操作被Subscribe()调用时,才会真正启动这些操作并计算出结果。而这是通过Subscribe()调用connect函数, connect函数最终调用op实现的。而op函数就是具体操作的重点,其根据之前赋值的相应属性值,计算出最终操作的结果并交给下一个操作。

首先,rxgo.go定义了整个项目中非常重要的Observable结构。类比transforms.go的实现方法,在实现filtering这组新操作时,也在rxgo.go定义的Observable结构中增加新的字段,用于存储上述filtering操作的关键属性。添加字段后的Observable结构的定义代码如下:

// An Observable is a 'collection of items that arrive over time'. Observables can be used to model asynchronous events.
// Observables can also be chained by operators to transformed, combined those items
// The Observable's operators, by default, run with a channel size of 128 elements except that the source (first) observable has no buffer
type Observable struct {
	Name string
	mu   sync.Mutex // lock all when creating subscriber
	//
	flip     interface{} // transformation function
	outflow  chan interface{}
	operator streamOperator
	// chain of Observables
	root *Observable
	next *Observable
	pred *Observable
	// control model
	threading ThreadModel //threading model. if this is root, it represents obseverOn model
	buf_len   uint
	// utility vars
	debug             Observer
	flip_sup_ctx      bool //indicate that flip function use context as first paramter
	flip_accept_error bool // indicate that flip function input's data is type interface{} or error
	debounce   time.Duration // 指定一段时间,仅在过了这段时间还没发射数据时才发射一个数据
	sample     time.Duration // 指定采样时间,每次会发射上次采样后的第一个数据
	takeSkipFlag bool          // 确定take/Skip操作的类型
	distinct   bool          // 是否去掉重复元素
	first      bool          // 选择第一个元素的标志
	last       bool          // 选择最后一个元素的标志
	elementAt  int           // 选择指定的索引元素的编号
	skip       int           // 正值表示跳过前几项元素,负值表示跳过后几项元素
	take       int           // 正值表示选择前几项元素,负值表示选择后几项元素
}

之后新建filtering.go文件,在其中类比transforms.go具体实现各个操作。

数据结构

由于下面filtering需要经常处理越界错误,因此定义了越界的自定义错误如下:

var OutOfBound = errors.New("Out of bound!") // 越界错误

此外,类比transforms.go中对transOperater的定义,定义过滤操作filterOperator的type如下:

// 过滤操作的type
type filterOperator struct {
	opFunc func(ctx context.Context, o *Observable, item reflect.Value, out chan interface{}) (end bool)
}

同样,对于创建一个新的FilterObservable,类比transforms.go中的对应代码定义FilterObservable的"构造函数"如下:

// 初始化FilterObservable的构造函数
func (parent *Observable) newFilterObservable(name string) (o *Observable) {
	//new Observable
	o = newObservable()
	o.Name = name

	//chain Observables
	parent.next = o
	o.pred = parent
	o.root = parent.root

	//set options
	o.buf_len = BufferLen
	return
}

Filter

该操作只发射通过了给定谓词测试的数据项。
在这里插入图片描述

Filter已经由老师在transforms.go中进行了实现,这里并不需要修改和重新实现。并且可以参考老师的实现方式,实现其他的filtering操作。

Debounce

Debounce只有在空闲了一段时间后才发射数据,通俗的说,就是如果一段时间没有操作,就执行一次操作。
在这里插入图片描述

Debounce操作符会过滤掉发射速率过快的数据项。可以根据要求完成Debounce操作的定义如下:

// 仅在过了一段指定的时间还没发射数据时才发射一个数据
func (parent *Observable) Debounce(_debounce_time time.Duration) (o *Observable) {
	o = parent.newFilterObservable("debounce")
	o.debounce, o.take, o.skip = _debounce_time, 0, 0//设置debounce时间
	o.operator = lastOperator
	return o
}

var debounceOperator = filterOperator{opFunc: func(ctx context.Context, o *Observable, x reflect.Value, out chan interface{}) (end bool) {
	var params = []reflect.Value{x}
	x = params[0]

	// 发射数据项
	if !end {
		end = o.sendToFlow(ctx, x.Interface(), out)
	}
	return
},
}

Distinct

Distinct操作过滤掉重复的数据项,也就是完成去重操作。

Distinct操作符会过滤掉重复的数据项。可以根据要求完成Distinct操作的定义如下:

// 抑制(过滤掉)重复的数据项
func (parent *Observable) Distinct() (o *Observable) {
	o = parent.newFilterObservable("distinct")
	o.distinct = true//设置distinct为true
	o.debounce, o.take, o.skip = 0, 0, 0
	o.operator = distinctOperator
	return o
}

var distinctOperator = filterOperator{opFunc: func(ctx context.Context, o *Observable, x reflect.Value, out chan interface{}) (end bool) {
	var params = []reflect.Value{x}
	x = params[0]

	// 发射数据项
	if !end {
		end = o.sendToFlow(ctx, x.Interface(), out)
	}
	return
},
}

ElementAt

ElementAt只发射第N项数据。
在这里插入图片描述
ElementAt操作符获取原始Observable发射的数据序列指定索引位置的数据项,然后当做自己的唯一数据发射。可以根据要求完成ElementAt操作的定义如下:

// 只发射第N项数据
func (parent *Observable) ElementAt(index int) (o *Observable) {
	o = parent.newFilterObservable("elementAt")
	o.debounce, o.skip, o.take, o.elementAt = 0, 0, 0, index + 1//设置ElementAt的索引
	o.operator = elementAtOperator
	return o
}

var elementAtOperator = filterOperator{opFunc: func(ctx context.Context, o *Observable, x reflect.Value, out chan interface{}) (end bool) {
	var params = []reflect.Value{x}
	x = params[0]

	// 发射数据项
	if !end {
		end = o.sendToFlow(ctx, x.Interface(), out)
	}
	return
},
}

First

只发射第一项(或者满足某个条件的第一项)数据

在这里插入图片描述
First操作符只获取第一项数据并将其当做自己的唯一数据发射。可以根据要求完成First操作的定义如下:

// 只发射第一项(或者满足某个条件的第一项)数据
func (parent *Observable) First() (o *Observable) {
	o = parent.newFilterObservable("first")
	o.first = true//设置只发送第一项
	o.debounce, o.take, o.skip = 0, 0, 0
	o.operator = firstOperator
	return o
}

var firstOperator = filterOperator{func(ctx context.Context, o *Observable, x reflect.Value, out chan interface{}) (end bool) {
	var params = []reflect.Value{x}
	x = params[0]

	// 发射数据项
	if !end {
		end = o.sendToFlow(ctx, x.Interface(), out)
	}
	return
},
}

IgnoreElements

不发射任何数据,只发射Observable的终止通知。
在这里插入图片描述
IgnoreElements操作符抑制原始Observable发射的所有数据,只允许它的终止通知(onError或onCompleted)通过。可以根据要求完成IgnoreElements操作的定义如下:

func (parent *Observable) IgnoreElements() (o *Observable) {
	o = parent.newFilterObservable("ignoreElements")
	o.operator = ignoreElementsOperator
	o.take = 0;
	return o
}

var ignoreElementsOperator = filterOperator{func(ctx context.Context, o *Observable, x reflect.Value, out chan interface{}) (end bool) {
	var params = []reflect.Value{x}
	x = params[0]

	// 发射数据项
	if !end {
		end = o.sendToFlow(ctx, x.Interface(), out)
	}
	return
},
}

Last

只发射最后一项(或者满足某个条件的最后一项)数据

Last操作符只获取最后一项数据并将其当做自己的唯一数据发射。可以根据要求完成Last操作的定义如下:

// 只发射最后一项(或者满足某个条件的最后一项)数据
func (parent *Observable) Last() (o *Observable) {
	o = parent.newFilterObservable("last")
	o.last = true//只发射最后一项数据
	o.debounce, o.take, o.skip = 0, 0, 0
	o.operator = lastOperator
	return o
}

var lastOperator = filterOperator{func(ctx context.Context, o *Observable, x reflect.Value, out chan interface{}) (end bool) {
	var params = []reflect.Value{x}
	x = params[0]

	// 发射数据项
	if !end {
		end = o.sendToFlow(ctx, x.Interface(), out)
	}
	return
},
}

Sample

Sample定期发射Observable最近发射的数据项。
在这里插入图片描述
Sample操作符定时查看一个Observable,然后发射自上次采样以来它最近发射的数据。如果自上次采样以来,原始Observable没有发射任何数据,这个操作返回的Observable在那段时间内也不会发射任何数据。可以根据要求完成Sample操作的定义如下:

// 定期发射Observable最近发射的数据项
func (parent *Observable) Sample(_sample_time time.Duration) (o *Observable) {
	o = parent.newFilterObservable("sample")
	o.debounce, o.skip, o.take, o.elementAt, o.sample = 0, 0, 0, 0, _sample_time//设置采样时间
	o.operator = sampleOperator
	return o
}

var sampleOperator = filterOperator{opFunc: func(ctx context.Context, o *Observable, x reflect.Value, out chan interface{}) (end bool) {
	var params = []reflect.Value{x}
	x = params[0]

	// 发射数据项
	if !end {
		end = o.sendToFlow(ctx, x.Interface(), out)
	}
	return
},
}

Skip

Skip抑制Observable发射的前N项数据。
在这里插入图片描述
使用Skip操作符,可以忽略Observable’发射的前N项数据,只保留之后的数据。可以根据要求完成Skip操作的定义如下:

// 抑制Observable发射的前N项数据
func (parent *Observable) Skip(num int) (o *Observable) {
	o = parent.newFilterObservable("skip")
	o.debounce, o.take, o.skip = 0, 0, num//设置跳过元素个数
	o.operator = skipOperator
	return o
}

var skipOperator = filterOperator{opFunc: func(ctx context.Context, o *Observable, x reflect.Value, out chan interface{}) (end bool) {
	var params = []reflect.Value{x}
	x = params[0]

	// 发射数据项
	if !end {
		end = o.sendToFlow(ctx, x.Interface(), out)
	}
	return
},
}

SkipLast

SkipLast抑制Observable发射的后N项数据

使用SkipLast操作符修改原始Observable,可以忽略Observable’发射的后N项数据,只保留前面的数据。可以根据要求完成SkipLast操作的定义如下:

// 抑制Observable发射的后N项数据
func (parent *Observable) SkipLast(num int) (o *Observable) {
	o = parent.newFilterObservable("skipLast")
	o.debounce, o.take, o.skip = 0, 0, -num//设置跳过尾部元素个数
	o.operator = skipLastOperator
	return o
}

var skipLastOperator = filterOperator{opFunc: func(ctx context.Context, o *Observable, x reflect.Value, out chan interface{}) (end bool) {
	var params = []reflect.Value{x}
	x = params[0]

	// 发射数据项
	if !end {
		end = o.sendToFlow(ctx, x.Interface(), out)
	}
	return
},
}

Take

Take只发射前面的N项数据。
在这里插入图片描述
使用Take操作符可以修改Observable的行为,只返回前面的N项数据,然后发射完成通知,忽略剩余的数据。可以根据要求完成Take操作的定义如下:

// 只发射前面的N项数据
func (parent *Observable) Take(num int) (o *Observable) {
	o = parent.newFilterObservable("Take")
	o.takeSkipFlag = true//为take
	o.debounce, o.skip, o.take = 0, 0, num//设置只发射前面元素的个数
	o.operator = takeOperator
	return o
}

var takeOperator = filterOperator{opFunc: func(ctx context.Context, o *Observable, x reflect.Value, out chan interface{}) (end bool) {
	var params = []reflect.Value{x}
	x = params[0]

	// 发射数据项
	if !end {
		end = o.sendToFlow(ctx, x.Interface(), out)
	}
	return
},
}

TakeLast

TakeLast发射Observable的最后N项数据。
在这里插入图片描述
使用TakeLast操作符修改原始Observable,可以只发射Observable’发射的后N项数据,忽略前面的数据。可以根据要求完成TakeLast操作的定义如下:

// 发射Observable发射的最后N项数据
func (parent *Observable) TakeLast(num int) (o *Observable) {
	o = parent.newFilterObservable("takeLast")
	o.takeSkipFlag = true//为take
	o.debounce, o.skip, o.take = 0, 0, -num//设置只发射后面元素的个数
	o.operator = takeLastOperator
	return o
}

var takeLastOperator = filterOperator{opFunc: func(ctx context.Context, o *Observable, x reflect.Value, out chan interface{}) (end bool) {
	var params = []reflect.Value{x}
	x = params[0]

	// 发射数据项
	if !end {
		end = o.sendToFlow(ctx, x.Interface(), out)
	}
	return
},
}

op函数

类比于tranrforms.go的框架。op函数实现了上述各个操作的具体操作和错误处理等功能。根据上面的定义描述,可以具体完成op函数如下,具体分析详见下面代码注释。

func (tsop filterOperator) op(ctx context.Context, o *Observable) {
	var _wait_group sync.WaitGroup

	var _out []interface{}

	in := o.pred.outflow// 输入
	out := o.outflow// 输出

	go func() {
		end := false// 结束标志
		_item_exist := make(map[interface{}]bool)// 用于标识数据项是否存在 以便去重

		start := time.Now()// 获取开始的时间
		sample_time := start// 采样时间为当前时间

		for x := range in {//遍历每个输入元素
			_interval := time.Since(start)//当前经过的时间
			_sample_interval := time.Since(sample_time)//距离上一次采样的时间
			start = time.Now()

			if end {// 结束则break
				break
			}

			xv := reflect.ValueOf(x)// 不可以将接口作为参数(指针)传递给GoRuntation,因为它可能会在外部更改其值
			if o.distinct && _item_exist[xv.Interface()] {
				continue
			}

			if o.debounce > time.Duration(0) && _interval < o.debounce {// 需要debounce 且距离上一个元素时间小于debounce_time 
				continue // 忽略当前元素
			}

			if o.sample > time.Duration(0) && _sample_interval < o.sample {// 需要采样 且距离上一个采样时间小于sample_time
				continue // 忽略当前元素
			}
			
			if e, ok := x.(error); ok && !o.flip_accept_error {// 如果flip不接受错误,则向流发送错误
				o.sendToFlow(ctx, e, out)
				continue
			}

			o.mu.Lock()//信号量加锁
			_out = append(_out, x)//加入x
			_item_exist[xv.Interface()] = true//设置存在
			o.mu.Unlock()//信号量解锁
			
			if o.elementAt > 0 {//取特定索引位置
				continue
			}

			if o.take != 0 || o.skip != 0 {//take或者skip
				continue
			}

			if o.last {//取结尾
				continue
			}

			// scheduler
			switch threading := o.threading; threading {
				case ThreadingDefault:
					for ; o.sample > 0 && sample_time.Add(o.sample).Before(time.Now()); {//到下一个采样时间点
						sample_time = sample_time.Add(o.sample)//增加采样时间
					}
					if tsop.opFunc(ctx, o, xv, out) {
						end = true// end为true
					}
				case ThreadingIO:
					fallthrough
				case ThreadingComputing:
					_wait_group.Add(1)//添加_wait_group
					for ; o.sample > 0 && sample_time.Add(o.sample).Before(time.Now()); {//到下一个采样时间点
						sample_time = sample_time.Add(o.sample)//增加采样时间
					}
					go func() {
						defer _wait_group.Done()
						if tsop.opFunc(ctx, o, xv, out) {
							end = true// end为true
						}
					}()
				default:
			}
			if o.first {//是第一个元素 需要break
				break
			}
		}

		if o.last && len(_out) > 0 {//处理last
			_wait_group.Add(1)
			go func() {
				defer _wait_group.Done()
				xv := reflect.ValueOf(_out[len(_out)-1])//获得最后一个元素
				tsop.opFunc(ctx, o, xv, out)
			}()
		}

		if o.take != 0 || o.skip != 0 {//处理take和skip
			_wait_group.Add(1)
			go func() {
				defer _wait_group.Done()
				var num int
				if o.takeSkipFlag {//根据标志选择take或者skip
					num = o.take
				} else {
					num = o.skip
				}
				new_in, err := seleteItems(o.takeSkipFlag, num, _out)//选择出对应的元素

				if err != nil {
					o.sendToFlow(ctx, err, out)//发送错误
				} else {
					xv := new_in
					for _, val := range xv {
						tsop.opFunc(ctx, o, reflect.ValueOf(val), out)
					}
				}
			}()
		}

		if o.elementAt != 0 {//处理选择某个索引元素
			if o.elementAt <= 0 || o.elementAt > len(_out) {//越界错误
				o.sendToFlow(ctx, OutOfBound, out)
			} else {
				xv := reflect.ValueOf(_out[o.elementAt - 1])//取对应元素
				tsop.opFunc(ctx, o, xv, out)
			}
		}

		_wait_group.Wait() //等待所有go程结束
		if (o.last || o.first) && len(_out) == 0 && !o.flip_accept_error {//对于这些还没有处理的情况
			o.sendToFlow(ctx, errors.New("There is no input!"), out)//缺少输入
		}
		o.closeFlow(out)
	}()

}

其中,上面用到的seleteItems函数根据具体的take和skip等参数,具体从输入的数据中选择出对应需要保留的数据发射,其实现如下,具体分析详见代码注释:

// 根据take和skip参数选择出元素
func seleteItems(flag bool, num int, in []interface{}) ([]interface{}, error) {
	if (flag && num > 0) || (!flag && num < 0) {// 取前面的 或者跳过后面的
		if !flag {
			num = len(in) + num// 转换为取前面的情况
		}
		if num >= len(in) || num <= 0 { // 越界
			return nil, OutOfBound
		}
		return in[:num], nil // 返回前面num个元素
	}

	if (flag && num < 0) || (!flag && num > 0) { // 取后面的 或者跳过前面的
		if flag {
			num = len(in) + num// 转换为取后面的情况
		}
		if num >= len(in) || num <= 0 { // 越界
			return nil, OutOfBound
		}
		return in[num:], nil // 返回后面num个元素
	}
	return nil, OutOfBound // 越界
}

单元测试

对于上面实现的操作,类比transforms_test.go,编写对于filtering.go的测试如下:

package rxgo_test

import (
	"time"
	"testing"

	"github.com/pmlpml/rxgo"
	"github.com/stretchr/testify/assert"
)

func TestDebounce(t *testing.T) {
	res := []int{}
	ob := rxgo.Just(10, 20, 30, 40, 50).Map(func(x int) int {
		switch x {
			case 10:
				time.Sleep(1 * time.Millisecond)
			case 20:
				time.Sleep(2 * time.Millisecond)
			case 30:
				time.Sleep(3 * time.Millisecond)
			case 40:
				time.Sleep(6 * time.Millisecond)
			default:
				time.Sleep(10 * time.Millisecond)
		}
		return x
	}).Debounce(3 * time.Millisecond)
	ob.Subscribe(func(x int) {
		res = append(res, x)
	})

	assert.Equal(t, []int{30, 40, 50}, res, "Debounce test failed!")
}

func TestDistinct(t *testing.T) {
	res := []int{}
	ob := rxgo.Just(10, 20, 10, 30, 40, 50, 30, 40).Distinct()
	ob.Subscribe(func(x int) {
		res = append(res, x)
	})
	assert.Equal(t, []int{10, 20, 30, 40, 50}, res, "Distinct test failed!")
}

func TestElementAt(t *testing.T) {
	res := []int{}
	ob := rxgo.Just(10, 20, 30, 40, 50).ElementAt(3)
	ob.Subscribe(func(x int) {
		res = append(res, x)
	})
	assert.Equal(t, []int{40}, res, "ElementAt test failed!")
}

func TestFirst(t *testing.T) {
	res := []int{}
	ob := rxgo.Just(10, 20, 30, 40, 50).First()
	ob.Subscribe(func(x int) {
		res = append(res, x)
	})

	assert.Equal(t, []int{10}, res, "First test failed!")
}

func TestLast(t *testing.T) {
	res := []int{}
	ob := rxgo.Just(10, 20, 30, 40, 50).Last()
	ob.Subscribe(func(x int) {
		res = append(res, x)
	})

	assert.Equal(t, []int{50}, res, "Last test failed!")
}

func TestSample(t *testing.T) {
	res := []int{}
	rxgo.Just(10, 20, 30, 40, 50).Map(func(x int) int {
		switch x {
			case 10:
				time.Sleep(10 * time.Millisecond)
			case 20:
				time.Sleep(20 * time.Millisecond)
			case 30:
				time.Sleep(40 * time.Millisecond)
			case 40:
				time.Sleep(80 * time.Millisecond)
			default:
				time.Sleep(160 * time.Millisecond)
		}
		return x
	}).Sample(20 * time.Millisecond).Subscribe(func(x int) {
		res = append(res, x)
	})
	assert.Equal(t, []int{20, 30, 40, 50}, res, "Sample test failed!")
}

func TestSkip(t *testing.T) {
	res := []int{}
	ob := rxgo.Just(10, 20, 30, 40, 50).Skip(2)
	ob.Subscribe(func(x int) {
		res = append(res, x)
	})
	assert.Equal(t, []int{30, 40, 50}, res, "Skip test failed!")
}

func TestSkipLast(t *testing.T) {
	res := []int{}
	ob := rxgo.Just(10, 20, 30, 40, 50).SkipLast(3)
	ob.Subscribe(func(x int) {
		res = append(res, x)
	})
	assert.Equal(t, []int{10, 20}, res, "SkipLast test failed!")
}

func TestTake(t *testing.T) {
	res := []int{}
	ob := rxgo.Just(11, 22, 33, 44, 55).Take(4)
	ob.Subscribe(func(x int) {
		res = append(res, x)
	})
	assert.Equal(t, []int{11, 22, 33, 44}, res, "Take test failed!")
}

func TestTakeLast(t *testing.T) {
	res := []int{}
	ob := rxgo.Just(10, 20, 30, 40, 50, 60).TakeLast(4)
	ob.Subscribe(func(x int) {
		res = append(res, x)
	})
	assert.Equal(t, []int{30, 40, 50, 60}, res, "TakeLast test failed!")
}

测试结果如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可见,通过了上面的各个单元测试(包括以前的单元测试),功能正常,符合要求。

功能测试

在目录"$GOPATH/src/gitee.com/alphabstc/useRxGo"下新建useFiltering.go文件,输入以下代码:

package main

import (
	"fmt"
	"time"

	"github.com/pmlpml/rxgo"
)

func main() {
	fmt.Println("Input items: 100, 200, 300, 400, 500")
	res := []int{}
	ob := rxgo.Just(100, 200, 300, 400, 500).Map(func(x int) int {
		switch x {
			case 10:
				time.Sleep(1 * time.Millisecond)
			case 20:
				time.Sleep(2 * time.Millisecond)
			case 30:
				time.Sleep(3 * time.Millisecond)
			case 40:
				time.Sleep(6 * time.Millisecond)
			default:
				time.Sleep(10 * time.Millisecond)
		}
		return x
	}).Debounce(3 * time.Millisecond)
	ob.Subscribe(func(x int) {
		res = append(res, x)
	})
	fmt.Print("After Debounce(3): ")
	for _, val := range res {
		fmt.Print(val, "  ")
	}
	fmt.Println("\n")

	fmt.Println("Input items: 10, 20, 30, 40, 50, 30, 40, 50, 30")
	res = []int{}
	ob = rxgo.Just(10, 20, 30, 40, 50, 30, 40, 50, 30).Map(func(x int) int {
		return x
	}).Distinct()
	ob.Subscribe(func(x int) {
		res = append(res, x)
	})
	fmt.Print("After Distinct: ")
	for _, val := range res {
		fmt.Print(val, "  ")
	}
	fmt.Println("\n")

	fmt.Println("Input items: 10, 20, 30, 40, 50, 60")
	res = []int{}
	ob = rxgo.Just(10, 20, 30, 40, 50, 60).Map(func(x int) int {
		return x
	}).ElementAt(2)
	ob.Subscribe(func(x int) {
		res = append(res, x)
	})
	fmt.Println("After ElementAt(2): ", res[0])
	fmt.Print("\n")

	fmt.Println("Input items: 10, 20, 30, 40, 50, 60")
	res = []int{}
	ob = rxgo.Just(10, 20, 30, 40, 50, 60).Map(func(x int) int {
		return x
	}).First()
	ob.Subscribe(func(x int) {
		res = append(res, x)
	})
	fmt.Println("After First: ", res[0])
	fmt.Print("\n")

	fmt.Println("Input items: 10, 20, 30, 40, 50, 60")
	res = []int{}
	ob = rxgo.Just(10, 20, 30, 40, 50, 60).Map(func(x int) int {
		return x
	}).Last()
	ob.Subscribe(func(x int) {
		res = append(res, x)
	})
	fmt.Println("After Last: ", res[0])
	fmt.Print("\n")

	fmt.Println("Input items: 10, 20, 30, 40, 50")
	res = []int{}
	rxgo.Just(10, 20, 30, 40, 50).Map(func(x int) int {
		switch x {
			case 10:
				time.Sleep(10 * time.Millisecond)
			case 20:
				time.Sleep(20 * time.Millisecond)
			case 30:
				time.Sleep(40 * time.Millisecond)
			case 40:
				time.Sleep(80 * time.Millisecond)
			default:
				time.Sleep(160 * time.Millisecond)
		}
		return x
	}).Sample(20 * time.Millisecond).Subscribe(func(x int) {
		res = append(res, x)
	})
	fmt.Print("After Sample: ")
	for _, val := range res {
		fmt.Print(val, "  ")
	}
	fmt.Println("\n")

	fmt.Println("Input items: 10, 20, 30, 40, 50, 60")
	res = []int{}
	ob = rxgo.Just(10, 20, 30, 40, 50, 60).Map(func(x int) int {
		return x
	}).Skip(2)
	ob.Subscribe(func(x int) {
		res = append(res, x)
	})
	fmt.Print("After Skip(2): ")
	for _, val := range res {
		fmt.Print(val, "  ")
	}
	fmt.Println("\n")

	fmt.Println("Input items: 10, 20, 30, 40, 50, 60")
	res = []int{}
	ob = rxgo.Just(10, 20, 30, 40, 50, 60).Map(func(x int) int {
		return x
	}).SkipLast(3)
	ob.Subscribe(func(x int) {
		res = append(res, x)
	})
	fmt.Print("After SkipLast(3): ")
	for _, val := range res {
		fmt.Print(val, "  ")
	}
	fmt.Println("\n")

	fmt.Println("Input items: 10, 20, 30, 40, 50, 60")
	res = []int{}
	ob = rxgo.Just(10, 20, 30, 40, 50, 60).Map(func(x int) int {
		return x
	}).Take(3)
	ob.Subscribe(func(x int) {
		res = append(res, x)
	})
	fmt.Print("After Take(3): ")
	for _, val := range res {
		fmt.Print(val, "  ")
	}
	fmt.Println("\n")

	fmt.Println("Input items: 10, 20, 30, 40, 50, 60")
	res = []int{}
	ob = rxgo.Just(10, 20, 30, 40, 50, 60).Map(func(x int) int {
		return x
	}).TakeLast(3)
	ob.Subscribe(func(x int) {
		res = append(res, x)
	})
	fmt.Print("After TakeLast(3): ")
	for _, val := range res {
		fmt.Print(val, "  ")
	}
	fmt.Print("\n")
}

运行结果如下:
在这里插入图片描述
在这里插入图片描述
可见,功能测试结果符合要求。

以上功能测试说明改进和添加功能的rxgo包功能基本正确。

生成API文档

先使用命令go get golang.org/x/tools/cmd/godoc来安装godoc。该命令会访问官网下载godoc,有可能访问超时。为此,需要在Bash下设置如下的环境变量:

export GOPROXY=https://goproxy.io
export GO111MODULE=on

这样就可以顺利安装godoc:
在这里插入图片描述
然后在bash下运行命令go build golang.org/x/tools/cmd/godoc
在这里插入图片描述

再运行godoc就可以在浏览器通过http://localhost:6060/来访问godoc了。
在这里插入图片描述
之后,还可以将文档导出出来:

godoc -url "http://localhost:6060/pkg/github.com/pmlpml/rxgo" > api.html

在这里插入图片描述

项目链接

https://gitee.com/alphabstc/service-computing-reactive-x

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值