服务计算 作业六 修改、改进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 基础上,

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

该库的基本组成:

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

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

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

项目实现

阅读文档,知道要做什么

响应式编程
Rx提供了一系列的操作符,你可以使用它们来过滤(filter)、选择(select)、变换(transform)、结合(combine)和组合(compose)多个Observable,这些操作符让执行和复合变得非常高效。

你可以把Observable当做Iterable的推送方式的等价物,使用Iterable,消费者从生产者那拉取数据,线程阻塞直至数据准备好。使用Observable,在数据准备好时,生产者将数据推送给消费者。数据可以同步或异步的到达,这种方式更灵活。

然后再阅读老师给的代码之后,可以发现其中transform等都是已经实现好的了,剩下需要我们补充的部分就是filter,它的功能是就是过滤、筛选发射的数据。对应的有以下几种具体操作:
在这里插入图片描述

实现思路

参考以上的功能之后,就可以开始编程实践了。

并且以上的功能每一个在官方文档中都有比较详细的说明,对于功能具体的逻辑解释也很清晰,所以接下来我就按照文档的思路一步一步实现filter.go。

基础部分(结构体定义、op函数、初始化函数)
  • 1.参考transform.go,定义一个新的结构体myfilterOperator
    这其实与transOperator的结构是一样的,因为两者都是对同样的对象进行操作,只是具体操作的内容不同而已。
// filter node implementation of streamOperator, used for filtering
type myfilterOperater struct {
	opFunc func(ctx context.Context, o *Observable, item reflect.Value, out chan interface{}) (end bool)
}
  • 2.然后就是op函数的实现
    这一部分与transform的op函数时基本一致的,只是参数需要修改为当前使用的myfilterOperator,然后其余照搬就好了。
    当然,这样的做法在后面会遇到问题,现在我们先不管,在后面又返回来修改。
func (fop myfilterOperater) 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 {
				continue
			}
			// 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 fop.opFunc(ctx, o, xv, out) {
					end = true
				}
			case ThreadingIO:
				fallthrough
			case ThreadingComputing:
				wg.Add(1)
				go func() {
					defer wg.Done()
					if fop.opFunc(ctx, o, xv, out) {
						end = true
					}
				}()
			default:
			}
		}

		wg.Wait() //waiting all go-routines completed
		o.closeFlow(out)
	}()
}
  • 3.一个初始化函数newFilterObservable
    这个函数用来初始化Observable,因为与tranform使用的参数不完全相同,所以就构造了一个新的初始化函数。
// 初始化一个过滤Observable
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
}
功能部分

对应官方文档中的具体功能的实现。

  • 1.Debounce
    功能是仅在过了一段指定的时间还没发射数据时才发射一个数据。
    在这里插入图片描述
//Debounce will filter the items which emit too fast.
//仅在过了一段指定的时间还没发射数据时才发射一个数据
func (parent *Observable) Debounce(time_debounce time.Duration) (o *Observable) {
	o = parent.newFilterObservable("debounce")
	o.flip_accept_error = false
	o.flip_sup_ctx = false
	o.flip = nil

	//count used for judging whether having emit during debounce
	count := 0
	o.operator = myfilterOperater{
		func(ctx context.Context, o *Observable, x reflect.Value, out chan interface{}) (end bool) {
			var params = []reflect.Value{x}
			var item = params[0] //简化userFuncCall

			count++
			var temp = count

			time.Sleep(time_debounce)

			if temp == count { //there has no emit
				end = o.sendToFlow(ctx, item.Interface(), out)
			}
			return
		}}
	return
}

实现思路很简单,就是给定一个计数器,然后在一个item进入输入流的时候,定义一个变量temp作为当前流中所有item个数,然后判断在进程sleep之后这两个变量是否相等,相等就意味着当前的item并没有在上一个已发送item的时间间隔内,可以再次发送,反之就不能发送。

  • 2.Distinct
    功能就是抑制(过滤掉)重复的数据项,只允许还没有发射过的数据项通过。
    在这里插入图片描述
//Distinct only emit items that have not been emited
//抑制(过滤掉)重复的数据项
func (parent *Observable) Distinct() (o *Observable) {
	o = parent.newFilterObservable("distinct")
	o.flip_accept_error = false
	o.flip_sup_ctx = false
	o.flip = nil

	//temp used for recording the item which have been  emited
	var temp = map[string]bool{}
	o.operator = myfilterOperater{
		func(ctx context.Context, o *Observable, x reflect.Value, out chan interface{}) (end bool) {
			var params = []reflect.Value{x}
			var item = params[0] //简化userFuncCall

			itemStr := fmt.Sprintf("%v", item)
			if _, ok := temp[itemStr]; !ok {
				temp[itemStr] = true
				end = o.sendToFlow(ctx, item.Interface(), out)
			}
			return
		}}
	return
}

这一部分我是通过一个map来记录已经发射的item,在接下来每一个到达输入流的item,都便利map看是否已发射,没有发射过的话就将它发射出去,然后保存到map里。

  • 3.ElementAt
    功能是只发射第N项数据。
    在这里插入图片描述
//ElementAt emit the item which is in the index
//只发射第N项数据
func (parent *Observable) ElementAt(index int) (o *Observable) {
	o = parent.newFilterObservable("elementAt")
	o.flip_accept_error = false
	o.flip_sup_ctx = false
	o.flip = nil

	//count is the index of current item
	count := 0
	o.operator = myfilterOperater{
		func(ctx context.Context, o *Observable, x reflect.Value, out chan interface{}) (end bool) {
			var params = []reflect.Value{x}
			var item = params[0] //简化userFuncCall

			if count == index {
				end = o.sendToFlow(ctx, item.Interface(), out)
			}
			count++
			return
		}}
	return
}

这也是比较简单的部分,ElementAt是一个带参数的函数,参数是需要发射出去的item的位置。所以我们就定义一个计数器,只有当计数器的值与这个位置的值相同的时候,才发射item。

  • 4.First
    顾名思义,就是发射第一个item。
//First emit the first item
//只发射第一项数据
func (parent *Observable) First() (o *Observable) {
	o = parent.newFilterObservable("first")
	o.flip_accept_error = false
	o.flip_sup_ctx = false
	o.flip = nil

	//count is the index of current item
	count := 0
	o.operator = myfilterOperater{
		func(ctx context.Context, o *Observable, x reflect.Value, out chan interface{}) (end bool) {
			var params = []reflect.Value{x}
			var item = params[0] //简化userFuncCall

			if count == 0 {
				end = o.sendToFlow(ctx, item.Interface(), out)
			}
			count++
			return
		}}
	return
}

它的功能相当简单,实现的时候就只需要让它在第一个item发射,其余位置不发射就可以搞定了。

  • 5.IgnoreElements
    功能是不发射任何数据,只发射Observable的终止通知。
//IgnoreElements do not emit any item
//不发射任何数据,只发射Observable的终止通知
func (parent *Observable) IgnoreElements() (o *Observable) {
	o = parent.newFilterObservable("ignoreElements")
	o.flip_accept_error = false
	o.flip_sup_ctx = false
	o.flip = nil

	o.operator = myfilterOperater{
		func(ctx context.Context, o *Observable, x reflect.Value, out chan interface{}) (end bool) {
			//不发送item,直接return
			return
		}}
	return
}

它的实现更简单,只要在在每次一个新的item进入到输入流时,都不发射,直到结束,就可以了。也就是说在整个过程中都没有发射动作的发生。

  • 6.Last
    功能是发射最后一个item。
    这里就要遇到问题了,我们只能在op中知道输入流结束,但是在opFunc中不能,所以就产生了问题,最后一个到底怎么找。
    为了解决这个问题,我们使用flip来作为缓存存储item,回到最开始实现op函数的时候,在哪里判断输入流的结束并发射flip的内容。

在op函数中加入一个判断:

//判断输入流结束
		if o.flip != nil {
			buffer := reflect.ValueOf(o.flip)
			if buffer.Kind() != reflect.Slice {
				panic("buffer is not a slice.")
			}
			for i := 0; i < buffer.Len(); i++ {
				o.sendToFlow(ctx, buffer.Index(i).Interface(), out)
			}
		}

实现last:

//Last emit the last item
//只发射最后一项数据
func (parent *Observable) Last() (o *Observable) {
	o = parent.newFilterObservable("last")
	o.flip_accept_error = false
	o.flip_sup_ctx = false

	o.operator = myfilterOperater{
		func(ctx context.Context, o *Observable, x reflect.Value, out chan interface{}) (end bool) {
			var params = []reflect.Value{x}
			var item = params[0] //简化userFuncCall

			o.flip = append([]interface{}{}, item.Interface())
			return false
		}}
	return
}
  • 7.skip、take
    skip功能是跳过n个item。
    take功能是发射前n个item。
    在这里插入图片描述

在这里插入图片描述

//Skip will skip the first n items
//抑制Observable发射的前N项数据
func (parent *Observable) Skip(n int) (o *Observable) {
	o = parent.newFilterObservable("skip")
	o.flip_accept_error = false
	o.flip_sup_ctx = false
	o.flip = nil

	//for recording the index of item
	count := 0
	o.operator = myfilterOperater{
		func(ctx context.Context, o *Observable, x reflect.Value, out chan interface{}) (end bool) {
			var params = []reflect.Value{x}
			var item = params[0] //简化userFuncCall

			if count >= n {
				o.sendToFlow(ctx, item.Interface(), out)
			}
			count++
			return
		}}
	return
}

同样,这也是一个带参数的函数,实现思路是通过一个计数器与当前item的位置进行比较,大于index的才发射;而take就正好相反,小于的就发射。

  • 8.SkipLast、TakeLast
    SkipLast的功能是跳过后n个item.
    TakeLast的功能是发射后n个item.
//SkipLast will skip the last n items
//抑制Observable发射的后N项数据
func (parent *Observable) SkipLast(n int) (o *Observable) {
	o = parent.newFilterObservable("skipLast")
	o.flip_accept_error = false
	o.flip_sup_ctx = false
	o.flip = nil

	//for recording the index of item
	count := 0
	var lasts []interface{}
	o.operator = myfilterOperater{
		func(ctx context.Context, o *Observable, x reflect.Value, out chan interface{}) (end bool) {

			if count == n {
				end = o.sendToFlow(ctx, lasts[0], out)
				lasts = lasts[1:]
			} else {
				count++
			}
			lasts = append(lasts, x.Interface())
			return
		}}
	return
}

跳过后n个与发射后n个的逻辑是一致的,对于这样的情况,我采用一个数组来作为缓冲,当这个数组满了之后,就输出第一个item,然后把新的加到后面,直到最后剩下的留在数组中的item,就是需要跳过的item;TakeLast的实现是和它对应的。

到此,filter的各个功能就已经完成了。下面进行一些简单的测试。

测试

功能测试

在进行功能测试之前,首先你需要把包下载下来,使用命令
go get gitee.com/richard_lrlrlr/hw6/rxgo
可以下载得到包,然后你可以放在你喜欢的位置,在我的项目中采用了如下的结构:在这里插入图片描述
然后,我写了如下的mian.go来进行功能测试:

package main

import (
	"fmt"

	rxgo "github.com/github-user/rxgo"
)

func main() {
	//测试Debounce
	fmt.Println("测试数据为:1, 2, 3, 4, 5")
	result := []int{}
	ob := rxgo.Just(1, 2, 3, 4, 5).Map(func(x int) int {
		return x
	}).Debounce(10000)
	ob.Subscribe(func(x int) {
		result = append(result, x)
	})
	fmt.Println("执行操作: Debounce(10000)")
	fmt.Println("预期结果: 1 2 3 4 5")
	fmt.Print("结果:  ")
	for _, val := range result {
		fmt.Print(val, "  ")
	}
	fmt.Print("\n\n")

	//测试Distinct
	fmt.Println("测试数据为:1, 2, 2, 3, 4, 5, 3")
	result = []int{}
	ob = rxgo.Just(1, 2, 2, 3, 4, 5, 3).Map(func(x int) int {
		return x
	}).Distinct()
	ob.Subscribe(func(x int) {
		result = append(result, x)
	})
	fmt.Println("执行操作: Distinct()")
	fmt.Println("预期结果: 1 2 3 4 5")
	fmt.Print("结果:  ")
	for _, val := range result {
		fmt.Print(val, "  ")
	}
	fmt.Print("\n\n")

	//测试ElementAt
	fmt.Println("测试数据为:1, 2, 3, 4, 5")
	result = []int{}
	ob = rxgo.Just(1, 2, 3, 4, 5).Map(func(x int) int {
		return x
	}).ElementAt(3)
	ob.Subscribe(func(x int) {
		result = append(result, x)
	})
	fmt.Println("执行操作: ElementAt(3)")
	fmt.Println("预期结果: 4")
	fmt.Println("结果:  ", result[0])
	fmt.Print("\n")

	//测试IgnoreElements
	fmt.Println("测试数据为:1, 2, 3, 4, 5")
	result = []int{}
	ob = rxgo.Just(1, 2, 3, 4, 5).Map(func(x int) int {
		return x
	}).IgnoreElements()
	ob.Subscribe(func(x int) {
		result = append(result, x)
	})
	fmt.Println("执行操作: IgnoreElements()")
	fmt.Println("预期结果: ")
	fmt.Println("结果:  长度为", len(result))
	fmt.Print("\n")

	//测试First
	fmt.Println("测试数据为:1, 2, 3, 4, 5")
	result = []int{}
	ob = rxgo.Just(1, 2, 3, 4, 5).Map(func(x int) int {
		return x
	}).First()
	ob.Subscribe(func(x int) {
		result = append(result, x)
	})
	fmt.Println("执行操作: First()")
	fmt.Println("预期结果: 1")
	fmt.Println("结果:  ", result[0])
	fmt.Print("\n")

	//测试Last
	fmt.Println("测试数据为:1, 2, 3, 4, 5")
	result = []int{}
	ob = rxgo.Just(1, 2, 3, 4, 5).Map(func(x int) int {
		return x
	}).Last()
	ob.Subscribe(func(x int) {
		result = append(result, x)
	})
	fmt.Println("执行操作: Last()")
	fmt.Println("预期结果: 5")
	fmt.Println("结果:  ", result[0])
	fmt.Print("\n")

	//测试Skip
	fmt.Println("测试数据为:1, 2, 3, 4, 5")
	result = []int{}
	ob = rxgo.Just(1, 2, 3, 4, 5).Map(func(x int) int {
		return x
	}).Skip(3)
	ob.Subscribe(func(x int) {
		result = append(result, x)
	})
	fmt.Println("执行操作: Skip(3)")
	fmt.Println("预期结果: 4 5")
	fmt.Print("结果:  ")
	for _, val := range result {
		fmt.Print(val, "  ")
	}
	fmt.Print("\n\n")

	//测试SkipLast
	fmt.Println("测试数据为:1, 2, 3, 4, 5")
	result = []int{}
	ob = rxgo.Just(1, 2, 3, 4, 5).Map(func(x int) int {
		return x
	}).SkipLast(3)
	ob.Subscribe(func(x int) {
		result = append(result, x)
	})
	fmt.Println("执行操作: SkipLast(3)")
	fmt.Println("预期结果: 1 2")
	fmt.Print("结果:  ")
	for _, val := range result {
		fmt.Print(val, "  ")
	}
	fmt.Print("\n\n")

	//测试Take
	fmt.Println("测试数据为:1, 2, 3, 4, 5")
	result = []int{}
	ob = rxgo.Just(1, 2, 3, 4, 5).Map(func(x int) int {
		return x
	}).Take(3)
	ob.Subscribe(func(x int) {
		result = append(result, x)
	})
	fmt.Println("执行操作: Take(3)")
	fmt.Println("预期结果: 1 2 3")
	fmt.Print("结果:  ")
	for _, val := range result {
		fmt.Print(val, "  ")
	}
	fmt.Print("\n\n")

	//测试TakeLast
	fmt.Println("测试数据为:1, 2, 3, 4, 5")
	result = []int{}
	ob = rxgo.Just(1, 2, 3, 4, 5).Map(func(x int) int {
		return x
	}).TakeLast(3)
	ob.Subscribe(func(x int) {
		result = append(result, x)
	})
	fmt.Println("执行操作: TakeLast(3)")
	fmt.Println("预期结果: 3 4 5")
	fmt.Print("结果:  ")
	for _, val := range result {
		fmt.Print(val, "  ")
	}
	fmt.Print("\n")
}

测试结果如下:
在这里插入图片描述

单元测试

单元测试的我采用给定一个期望结果,然后和处理后的结果比较的方法进行测试,就比如测试ElementAt函数:

func TestElementAt(t *testing.T) {
	result := []int{}
	temp := rxgo.Just(1, 2, 3, 4, 5).Map(func(x int) int {
		return x
	}).ElementAt(3)
	temp.Subscribe(func(x int) {
		result = append(result, x)
	})

	assert.Equal(t, []int{4}, result, "ElementAt Test Failed!")
}

同理的,我的其他测试函数也是按照这个逻辑来实现的,完整代码如下:

package rxgo_test

import (
	"testing"

	"github.com/github-user/rxgo"
	"github.com/stretchr/testify/assert"
)

func TestDebounce(t *testing.T) {
	result := []int{}
	temp := rxgo.Just(1, 2, 3, 4, 5).Map(func(x int) int {
		return x
	}).Debounce(10000)
	temp.Subscribe(func(x int) {
		result = append(result, x)
	})

	assert.Equal(t, []int{1, 2, 3, 4, 5}, result, "Debounce Test Failed!")
}

func TestDistinct(t *testing.T) {
	result := []int{}
	temp := rxgo.Just(1, 2, 2, 3, 4, 5, 3).Map(func(x int) int {
		return x
	}).Distinct()
	temp.Subscribe(func(x int) {
		result = append(result, x)
	})

	assert.Equal(t, []int{1, 2, 3, 4, 5}, result, "Distinct Test Failed!")
}

func TestElementAt(t *testing.T) {
	result := []int{}
	temp := rxgo.Just(1, 2, 3, 4, 5).Map(func(x int) int {
		return x
	}).ElementAt(3)
	temp.Subscribe(func(x int) {
		result = append(result, x)
	})

	assert.Equal(t, []int{4}, result, "ElementAt Test Failed!")
}

func TestIgnoreElements(t *testing.T) {
	result := []int{}
	temp := rxgo.Just(1, 2, 3, 4, 5).Map(func(x int) int {
		return x
	}).IgnoreElements()
	temp.Subscribe(func(x int) {
		result = append(result, x)
	})

	assert.Equal(t, []int{}, result, "ElementAt Test Failed!")
}

func TestFirst(t *testing.T) {
	result := []int{}
	temp := rxgo.Just(1, 2, 3, 4, 5).Map(func(x int) int {
		return x
	}).First()
	temp.Subscribe(func(x int) {
		result = append(result, x)
	})

	assert.Equal(t, []int{1}, result, "First Test Failed!")
}

func TestLast(t *testing.T) {
	result := []int{}
	temp := rxgo.Just(1, 2, 3, 4, 5).Map(func(x int) int {
		return x
	}).Last()
	temp.Subscribe(func(x int) {
		result = append(result, x)
	})

	assert.Equal(t, []int{5}, result, "Last Test Failed!")
}

func TestSkip(t *testing.T) {
	result := []int{}
	temp := rxgo.Just(1, 2, 3, 4, 5).Map(func(x int) int {
		return x
	}).Skip(3)
	temp.Subscribe(func(x int) {
		result = append(result, x)
	})

	assert.Equal(t, []int{4, 5}, result, "Skip Test Failed!")
}

func TestSkipLast(t *testing.T) {
	result := []int{}
	temp := rxgo.Just(1, 2, 3, 4, 5).Map(func(x int) int {
		return x
	}).SkipLast(3)
	temp.Subscribe(func(x int) {
		result = append(result, x)
	})

	assert.Equal(t, []int{1, 2}, result, "SkipLast Test Failed!")
}

func TestTake(t *testing.T) {
	result := []int{}
	temp := rxgo.Just(1, 2, 3, 4, 5).Map(func(x int) int {
		return x
	}).Take(3)
	temp.Subscribe(func(x int) {
		result = append(result, x)
	})

	assert.Equal(t, []int{1, 2, 3}, result, "Take Test Failed!")
}

func TestTakeLast(t *testing.T) {
	result := []int{}
	temp := rxgo.Just(1, 2, 3, 4, 5).Map(func(x int) int {
		return x
	}).TakeLast(3)
	temp.Subscribe(func(x int) {
		result = append(result, x)
	})

	assert.Equal(t, []int{3, 4, 5}, result, "TakeLast Test Failed!")
}

测试结果如下:
在这里插入图片描述
注:在测试的时候我是几个测试文件一起运行的,所以图中还有老师给的测试文件的结果。

API文档

直接使用godoc,生成文档
在这里插入图片描述

最后附上完整项目链接:https://gitee.com/richard_lrlrlr/service-computing/tree/master/hw6

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值