服务计算(6)——修改、改进 RxGo 包

本文档介绍了如何在RxGo基础上修改和改进filtering操作,包括Debounce、Distinct、ElementAt等。详细阐述了每个操作的实现原理,并提供了测试方法,展示了如何在实际项目中应用这些修改。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、简介

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

2、课程任务

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

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

该库的基本组成:

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

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

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

filtering操作的实现

首先,filtering 有如下功能:

  • 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

其中Filter这一功能老师在transforms.go里面已经实现了,因此在这里我们也不需要再去实现。

首先,结构体filteringOperator可以直接从transforms.go里照搬过去,改改名字。

type filteringOperator struct {
	opFunc func(ctx context.Context, o *Observable, item reflect.Value, out chan interface{}) (end bool)
}

op大抵也是照搬过去的,只是有些许改变:

func (ftop filteringOperator) op(ctx context.Context, o *Observable) {
	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 ftop.opFunc(ctx, o, xv, out) {
					end = true
				}
			case ThreadingIO:
				fallthrough
			case ThreadingComputing:
				wg.Add(1)
				go func() {
					defer wg.Done()
					if ftop.opFunc(ctx, o, xv, out) {
						end = true
					}
				}()
			default:
			}
		}
		if o.flip != nil {
			buffer := (reflect.ValueOf(o.flip))
			if buffer.Kind() != reflect.Slice {
				panic("flip is not buffer")
			}
			for i := 0; i < buffer.Len(); i++ {
				o.sendToFlow(ctx, buffer.Index(i).Interface(), out)
			}
		}
		wg.Wait() //waiting all go-routines completed
		o.closeFlow(out)
	}()
}

前面一部分都是相同的,只是改改变量名,值得注意的是,后续会有Last/TakeLast操作,需要一个缓存来实现,这里就直接通过Observable里面的flip来完成。

func (parent *Observable) Debounce(timespan time.Duration) (o *Observable) {
	o = parent.newFilterObservable("debounce")
	o.flip_accept_error = true
	o.flip_sup_ctx = true
	count := 0
	o.operator = filteringOperator{
		opFunc: func(ctx context.Context, o *Observable, item reflect.Value, out chan interface{}) (end bool) {
			count++
			go func() {
				tempCount := count
				time.Sleep(timespan)
				select {
				case <-ctx.Done():
					return
				default:
					if tempCount == count {
						o.sendToFlow(ctx, item.Interface(), out)
					}
				}
			}()
			return false
		},
	}
	return o
}

Debounce的功能就是消除抖动,在这里,我就简单地用一个go程+一个延时函数来实现,若是过了这一段时间还没有新的item到来(在这里表现为count并未改变),再把之前的那个item输出出去。

在这里插入图片描述

func (parent *Observable) Distinct() (o *Observable) {
	o = parent.newFilterObservable("distinct")
	o.flip_accept_error = true
	o.flip_sup_ctx = true
	m := map[string]bool{}
	o.operator = filteringOperator{
		opFunc: func(ctx context.Context, o *Observable, item reflect.Value, out chan interface{}) (end bool) {
			itemStr := fmt.Sprintf("%v", item)
			if _, ok := m[itemStr]; !ok {
				m[itemStr] = true
				o.sendToFlow(ctx, item.Interface(), out)
			}
			return false
		},
	}
	return o
}

Distinct就是将重复的数据过滤掉,这里我用一个map来判断当前数据是否是之前就出现过了。通过fmt.Sprintf来将item转化为相应的String,或许也可以通过json来完成,上一次作业实现的对象序列化也可以派上用场了。

在这里插入图片描述

func (parent *Observable) ElementAt(num int) (o *Observable) {
	o = parent.newFilterObservable("elementAt.n")
	o.flip_accept_error = true
	o.flip_sup_ctx = true
	count := 0
	o.operator = filteringOperator{
		opFunc: func(ctx context.Context, o *Observable, item reflect.Value, out chan interface{}) (end bool) {
			if count == num {
				o.sendToFlow(ctx, item.Interface(), out)
				return true
			}
			count++
			return false
		},
	}

	return o
}

ElementAt直接就是发送对应第n项的数据,因此直接用一个count来计数就可以了。

在这里插入图片描述

func (parent *Observable) First() (o *Observable) {
	o = parent.newFilterObservable("first")
	o.flip_accept_error = true
	o.flip_sup_ctx = true
	o.operator = filteringOperator{
		opFunc: func(ctx context.Context, o *Observable, item reflect.Value, out chan interface{}) (end bool) {
			o.sendToFlow(ctx, item.Interface(), out)
			return true
		},
	}
	return o
}

First的功能是发射第一项数据,因而直接发送第一项就行了。

在这里插入图片描述

func (parent *Observable) IgnoreElements() (o *Observable) {
	o = parent.newFilterObservable("ignoreElements")
	o.flip_accept_error = true
	o.flip_sup_ctx = true
	o.operator = filteringOperator{
		opFunc: func(ctx context.Context, o *Observable, item reflect.Value, out chan interface{}) (end bool) {
			return false
		},
	}
	return o
}

IgnoreElements不发射任何数据,只发射Observable的终止通知,可以说这一个是最简单的了,因为几乎什么事都不用干。

在这里插入图片描述

func (parent *Observable) Last() (o *Observable) {
	o = parent.newFilterObservable("last")
	o.flip_accept_error = true
	o.flip_sup_ctx = true
	o.operator = filteringOperator{
		opFunc: func(ctx context.Context, o *Observable, item reflect.Value, out chan interface{}) (end bool) {
			o.flip = append([]interface{}{}, item.Interface())
			return false
		},
	}
	return o
}

Last是要发送最后一项数据,前面说的flip就派上用场了,把它当作一个缓存来使用,实现一定的记忆化。这是因为在监听数据的时候,我们并不知道哪一个是真正的“最后一个”。

在这里插入图片描述

func (parent *Observable) Sample(sample chan interface{}) (o *Observable) {
	o = parent.newFilterObservable("sample")
	o.flip_accept_error = true
	o.flip_sup_ctx = true
	var latest interface{} = nil
	o.operator = filteringOperator{
		opFunc: func(ctx context.Context, o *Observable, item reflect.Value, out chan interface{}) (end bool) {
			latest = item.Interface()
			go func() {
				tempEnd := true
				for tempEnd {
					select {
					case <-ctx.Done():
						tempEnd = true
					case <-sample:
						if latest != nil {
							if o.sendToFlow(ctx, latest, out) {
								tempEnd = false
							}
							latest = nil
						}
					}
				}
			}()
			return false
		},
	}
	return o
}

Sample就是定期发射Observable最近发射的数据项,在这里我的实现思路就是持续监听管道Sample,一旦监听到消息就把当前最新的数据项发送出去,思路倒是这样没错,只是我的这个没错,只是这个实现方式倒是感觉有点怪怪的,只不过还没想到其他高端一点的实现

在这里插入图片描述

func (parent *Observable) Skip(num int) (o *Observable) {
	o = parent.newFilterObservable("skip.n")
	o.flip_accept_error = true
	o.flip_sup_ctx = true
	count := 0
	o.operator = filteringOperator{
		opFunc: func(ctx context.Context, o *Observable, item reflect.Value, out chan interface{}) (end bool) {
			count++
			if count > num {
				o.sendToFlow(ctx, item.Interface(), out)
			}
			return false
		},
	}

	return o
}

Skip就是跳过前n个,也就是说用一个count来计数,计到n个之后才发送。

在这里插入图片描述

func (parent *Observable) SkipLast(num int) (o *Observable) {
	o = parent.newFilterObservable("skipLast.n")
	o.flip_accept_error = true
	o.flip_sup_ctx = true
	count := 0
	var lasts []interface{}
	o.operator = filteringOperator{
		opFunc: func(ctx context.Context, o *Observable, item reflect.Value, out chan interface{}) (end bool) {
			if count == num {
				o.sendToFlow(ctx, lasts[0], out)
				lasts = lasts[1:]
			} else {
				count++
			}
			lasts = append(lasts, item.Interface())
			return false
		},
	}

	return o
}

SkipLast要跳过后n个。

在这里插入图片描述

根据官方文档的实例来看,其实也就是往后延n个,因此在这里我自定义了一个缓存来实现延后n个。

func (parent *Observable) Take(num int) (o *Observable) {
	o = parent.newFilterObservable("take.n")
	o.flip_accept_error = true
	o.flip_sup_ctx = true
	count := 0
	o.operator = filteringOperator{
		opFunc: func(ctx context.Context, o *Observable, item reflect.Value, out chan interface{}) (end bool) {
			count++
			if count > num {
				return true
			}
			o.sendToFlow(ctx, item.Interface(), out)
			return false
		},
	}

	return o
}

Take,取前面n项数据,同样也是用count来计数。

在这里插入图片描述

func (parent *Observable) TakeLast(num int) (o *Observable) {
	o = parent.newFilterObservable("takeLast.n")
	o.flip_accept_error = true
	o.flip_sup_ctx = true
	count := 0
	var lasts []interface{}
	o.operator = filteringOperator{
		opFunc: func(ctx context.Context, o *Observable, item reflect.Value, out chan interface{}) (end bool) {
			count++
			if count <= num {
				lasts = append(lasts, item.Interface())
			} else {
				lasts = lasts[1:]
				lasts = append(lasts, item.Interface())
			}
			o.flip = lasts
			return false
		},
	}

	return o
}

TakeLast的话,跟前面的Last有些许类似,都是通过flip来实现了。

在这里插入图片描述

至此,filtering的各个功能都实现得差不多了,除了Debounce跟Sample自我感觉有点不够完善以外,其他的也都大抵让人满意。

测试

新建一个文件filtering_test.go,在里面测试上述的各项功能。

package rxgo

import (
	"fmt"
	"time"
)

func ExampleDistinct() {
	Just(22, 12, 12, 13, 7, 5, 6, 22).Distinct().Subscribe(func(x int) {
		fmt.Print(x)
	})
	fmt.Println()
	//Output:221213756
}

func ExampleElementAt() {
	Just(2, 1, 12, 13, 17, 5, 6, 22).ElementAt(5).Subscribe(func(x int) {
		fmt.Print(x)
	})
	fmt.Println()
	//Output:5
}

func ExampleFirst() {
	Just(23, 11, 2, 3, 1, 25, 66).First().Subscribe(func(x int) {
		fmt.Print(x)
	})
	fmt.Println()
	//Output:23
}

func ExampleIgnoreElements() {
	Just(3, 11, 12, 15, 2, 6).IgnoreElements().Subscribe(func(x int) {
		fmt.Print(x)
	})
	fmt.Println()
	//Output:
}

func ExampleLast() {
	Just(33, 1, 0, 215, 4, 6).Last().Subscribe(func(x int) {
		fmt.Print(x)
	})
	fmt.Println()
	//Output:6
}

func ExampleSample() {
	observableP := make(chan interface{})
	go func() {
		Just(1, 2, 3, 4, 5).Map(func(x int) int {
			switch x {
			case 1:
				time.Sleep(0 * time.Millisecond)
			case 2:
				time.Sleep(1 * time.Millisecond)
			case 3:
				time.Sleep(2 * time.Millisecond)
			case 4:
				time.Sleep(2 * time.Millisecond)
			default:
				time.Sleep(2 * time.Millisecond)
			}
			return x
		}).Subscribe(func(x int) {
			observableP <- x
		})
	}()
	Just(1, 2, 3, 4, 5).Map(func(x int) int {
		time.Sleep(2 * time.Millisecond)
		return x
	}).Sample(observableP).Subscribe(func(x int) {
		fmt.Print(x)
	})
	fmt.Println()
	//Output:123
}

func ExampleSkip() {
	Just(3, 21, 0, 25, 24, 63, 77).Skip(2).Subscribe(func(x int) {
		fmt.Print(x)
	})
	fmt.Println()
	//Output:025246377
}

func ExampleSkipLast() {
	Just(3, 21, 0, 25, 24, 63, 77).SkipLast(2).Subscribe(func(x int) {
		fmt.Print(x)
	})
	fmt.Println()
	//Output:32102524
}

func ExampleTake() {
	Just(3, 21, 0, 25, 24, 63, 77).Take(4).Subscribe(func(x int) {
		fmt.Print(x)
	})
	fmt.Println()
	//Output:321025
}

func ExampleTakeLast() {
	Just(3, 21, 0, 25, 24, 63, 77).TakeLast(4).Subscribe(func(x int) {
		fmt.Print(x)
	})
	fmt.Println()
	//Output:25246377
}

func ExampleDebounce() {
	time.Sleep(250 * time.Millisecond)
	Just(0, 1, 2, 3, 4, 5).Map(func(x int) int {
		switch x {
		case 0:
			time.Sleep(0 * time.Millisecond)
		case 1:
			time.Sleep(1 * time.Millisecond)
		case 2:
			time.Sleep(3 * time.Millisecond)
		case 3:
			time.Sleep(3 * time.Millisecond)
		case 4:
			time.Sleep(1 * time.Millisecond)
		default:
			time.Sleep(1 * time.Millisecond)
		}
		return x
	}).Debounce(2 * time.Millisecond).Subscribe(func(x int) {
		fmt.Print(x)
	})
	fmt.Println()
	//Output:12
}

执行指令go test -v,能够看到测试结果。

在这里插入图片描述

然后新建一个文件main.go,通过import RxGo "github.com/pmlpml/rxgo"来导入rxgo包,再去随便测试一下刚刚实现的功能。

package main

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

func main() {
	RxGo.Just(22, 12, 12, 13, 7, 5, 6, 22).Distinct().Subscribe(func(x int) {
		fmt.Print(x)
	})
	fmt.Println()

	RxGo.Just(2, 1, 12, 13, 17, 5, 6, 22).ElementAt(5).Subscribe(func(x int) {
		fmt.Print(x)
	})
	fmt.Println()

	RxGo.Just(23, 11, 2, 3, 1, 25, 66).First().Subscribe(func(x int) {
		fmt.Print(x)
	})
	fmt.Println()

	RxGo.Just(3, 11, 12, 15, 2, 6).IgnoreElements().Subscribe(func(x int) {
		fmt.Print(x)
	})
	fmt.Println()

	RxGo.Just(33, 1, 0, 215, 4, 6).Last().Subscribe(func(x int) {
		fmt.Print(x)
	})
	fmt.Println()

	RxGo.Just(3, 21, 0, 25, 24, 63, 77).Skip(2).Subscribe(func(x int) {
		fmt.Print(x)
	})
	fmt.Println()

	RxGo.Just(3, 21, 0, 25, 24, 63, 77).SkipLast(2).Subscribe(func(x int) {
		fmt.Print(x)
	})
	fmt.Println()

	RxGo.Just(3, 21, 0, 25, 24, 63, 77).Take(4).Subscribe(func(x int) {
		fmt.Print(x)
	})
	fmt.Println()
	
	RxGo.Just(3, 21, 0, 25, 24, 63, 77).TakeLast(4).Subscribe(func(x int) {
		fmt.Print(x)
	})
	fmt.Println()

	observableP := make(chan interface{})
	go func() {
		RxGo.Just(1, 2, 3, 4, 5).Map(func(x int) int {
			switch x {
			case 1:
				time.Sleep(3 * time.Millisecond)
			case 2:
				time.Sleep(1 * time.Millisecond)
			case 3:
				time.Sleep(2 * time.Millisecond)
			case 4:
				time.Sleep(2 * time.Millisecond)
			default:
				time.Sleep(1 * time.Millisecond)
			}
			return x
		}).Subscribe(func(x int) {
			observableP <- x
		})
	}()
	RxGo.Just(1, 2, 3, 4, 5).Map(func(x int) int {
		time.Sleep(2*time.Millisecond)
		return x
	}).Sample(observableP).Subscribe(func(x int) {
		fmt.Print(x)
	})
	fmt.Println()

	RxGo.Just(0, 1, 1, 3, 1, 5).Map(func(x int) int {
		switch x {
		case 0:
			time.Sleep(0 * time.Millisecond)
		case 1:
			time.Sleep(1 * time.Millisecond)
		case 3:
			time.Sleep(2 * time.Millisecond)
		case 5:
			time.Sleep(3 * time.Millisecond)
		default:
			time.Sleep(1 * time.Millisecond)
		}
		return x
	}).Debounce(2 * time.Millisecond).Subscribe(func(x int) {
		fmt.Print(x)
	})
	fmt.Println()

}

执行go run main.go可以看到结果。

在这里插入图片描述

作业提交

我的完整代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值