服务计算 – 修改、改进 RxGo 包


ReactiveX

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

尝试使用pmlpml/RxGo

github链接
照着readme尝试进行使用

hello world

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
    "fmt"

    RxGo "github.com/pmlpml/rxgo"
)

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

hello world结果如图
在这里插入图片描述

链式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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.Print(x)
    })
}

链式测试,此次测试先用start定了个0112358的源observable,随后用Map对对象内的数全进行了修改,最后用Subscribe打印出
在这里插入图片描述

连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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)
    })
}

这个主要测试是否是深修改,结果如下
在这里插入图片描述

实验部份,实现Filtering

实验过程

首先要知道实现方法,参照老师已经实现了的filter,观察他的调用函数顺序
Start()只是创建了个observable,并赋予了它的基础属性,直到Subscribe()上才是最终启动
Subscribe()中又动用到了connect(), connect()又动用到了op,这个op接口才是自己需要实现的

新开了个filterings.go之后就开始实现以下函数,
首先把op跟transOperator直接搬过来,改下名字跟类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
type filteringsOperater struct {<!-- -->
    opFunc func(ctx context.Context, o *Observable, item reflect.Value, out chan interface{<!-- -->}) (end bool)
}

func (tsop filteringsOperater) 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 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)
    }()
}

之后开始尝试实现下面的功能,出现问题了在对上面做改动
先装上 “github.com/stretchr/testify/assert”,“github.com/davecgh/go-spew/spew”,“github.com/pmezard/go-difflib/difflib”
再写这十二个功能的测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package rxgo_test

import (
    "testing"
    "time"

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

func TestDebounce(t *testing.T) {<!-- -->
    res := []int{<!-- -->}
    ob := rxgo.Just(1, 2, 3, 4, 5, 6, 7, 8, 9).Map(func(x int) int {<!-- -->
        if x == 2 || x == 5 {<!-- -->
            time.Sleep(3 * time.Millisecond)
        } else {<!-- -->
            time.Sleep(1 * time.Millisecond)
        }
        return x
    }).Debounce(2 * time.Millisecond)

    ob.Subscribe(func(x int) {<!-- -->
        res = append(res, x)
    })

    assert.Equal(t, []int{<!-- -->1, 4, 9}, res, "debounce error")
}

func TestDistinct(t *testing.T) {<!-- -->
    res := []int{<!-- -->}
    ob := rxgo.Just(1, 2, 3, 3, 4, 5, 6, 7, 8, 9).Map(func(x int) int {<!-- -->
        time.Sleep(1 * time.Millisecond)
        return x
    }).Distinct()

    ob.Subscribe(func(x int) {<!-- -->
        res = append(res, x)
    })

    assert.Equal(t, []int{<!-- -->1, 2, 3, 4, 5, 6, 7, 8, 9}, res, "distinct error")
}

func TestElementAt(t *testing.T) {<!-- -->
    res := []int{<!-- -->}
    ob := rxgo.Just(1, 2, 3, 4, 5, 6, 7, 8, 9).Map(func(x int) int {<!-- -->
        time.Sleep(1 * time.Millisecond)
        return x
    }).ElementAt(1)

    ob.Subscribe(func(x int) {<!-- -->
        res = append(res, x)
    })

    assert.Equal(t, []int{<!-- -->2}, res, "elementAt error")
}

func TestFirst(t *testing.T) {<!-- -->
    res := []int{<!-- -->}
    ob := rxgo.Just(1, 2, 3, 4, 5, 6, 7, 8, 9).Map(func(x int) int {<!-- -->
        time.Sleep(1 * time.Millisecond)
        return x
    }).First()

    ob.Subscribe(func(x int) {<!-- -->
        res = append(res, x)
    })

    assert.Equal(t, []int{<!-- -->1}, res, "first error")
}

func TestIgnoreElements(t *testing.T) {<!-- -->
    res := []int{<!-- -->}
    ob := rxgo.Just(1, 2, 3, 4, 5, 6, 7, 8, 9).IgnoreElements()

    ob.Subscribe(func(x int) {<!-- -->
        res = append(res, x)
    })

    assert.Equal(t, []int{<!-- -->}, res, "ignore element error")
}

func TestLast(t *testing.T) {<!-- -->
    res := []int{<!-- -->}
    ob := rxgo.Just(1, 2, 3, 4, 5, 6, 7, 8, 9).Last()

    ob.Subscribe(func(x int) {<!-- -->
        res = append(res, x)
    })

    assert.Equal(t, []int{<!-- -->9}, res, "last error")
}

func TestSkip(t *testing.T) {<!-- -->
    res := []int{<!-- -->}
    ob := rxgo.Just(1, 2, 3, 4, 5, 6, 7, 8, 9).Map(func(x int) int {<!-- -->
        time.Sleep(1 * time.Millisecond)
        return x
    }).Skip(3)

    ob.Subscribe(func(x int) {<!-- -->
        res = append(res, x)
    })

    assert.Equal(t, []int{<!-- -->4, 5, 6, 7, 8, 9}, res, "skip error")
}

func TestSkiplast(t *testing.T) {<!-- -->
    res := []int{<!-- -->}
    ob := rxgo.Just(1, 2, 3, 4, 5, 6, 7, 8, 9).Map(func(x int) int {<!-- -->
        time.Sleep(1 * time.Millisecond)
        return x
    }).Skiplast(6)

    ob.Subscribe(func(x int) {<!-- -->
        res = append(res, x)
    })

    assert.Equal(t, []int{<!-- -->1, 2, 3}, res, "skiplast error")
}

func TestTake(t *testing.T) {<!-- -->
    res := []int{<!-- -->}
    ob := rxgo.Just(1, 2, 3, 4, 5, 6, 7, 8, 9).Map(func(x int) int {<!-- -->
        time.Sleep(1 * time.Millisecond)
        return x
    }).Take(3)

    ob.Subscribe(func(x int) {<!-- -->
        res = append(res, x)
    })

    assert.Equal(t, []int{<!-- -->1, 2, 3}, res, "take error")
}

func TestTakelast(t *testing.T) {<!-- -->
    res := []int{<!-- -->}
    ob := rxgo.Just(1, 2, 3, 4, 5, 6, 7, 8, 9).Map(func(x int) int {<!-- -->
        time.Sleep(1 * time.Millisecond)
        return x
    }).Takelast(3)

    ob.Subscribe(func(x int) {<!-- -->
        res = append(res, x)
    })

    assert.Equal(t, []int{<!-- -->7, 8, 9}, res, "takelast error")
}

写完测试后就是单元实现,实现就不放具体代码了,简要说明一下每个函数在干啥就是了

  1. Debounce
    Debounce的参数代表从后往前数间隔时间需大于参数才会被筛选出来,主要作用是过滤发射数据过快的项
    单元测试结果
    在这里插入图片描述

  2. Distinct
    去掉重复的项
    在这里插入图片描述
    这里着重讲一下,本来在测试的时后是没有加map(给每个项延迟避免并发),可能是我实现的时后没有加互斥锁,导致每次测试顺序都不对,避免麻烦就在测试的时后加了延迟。(码力不足见量)

  3. ElementAt
    返回在该下标的项
    在这里插入图片描述

  4. Filter
    filter老师已经实现了就不重复做了

  5. First
    返回第一项
    在这里插入图片描述

  6. IgnoreElements
    啥都不返回
    在这里插入图片描述

  7. Last
    返回最后一个,由于不知道输入流什么时候结束,所以每输入进一个就存一个,直到没东西为止才输出。
    由于使用了filter做判断,所以对op做了简单修改

    加了这个

    1
    2
    3
    4
    5
    6
    7
    8
    9
        if o.flip != nil {<!-- -->
            buffer, ok := reflect.ValueOf(o.flip).Interface().([]reflect.Value)
            if !ok {<!-- -->
                panic("filter.go(op): buffer is not a slice")
            }
            for _, v := range buffer{<!-- -->
                o.sendToFlow(ctx, v.Interface(), out)
            }
        }

    在这里插入图片描述

  8. Skip
    跳过前n项
    在这里插入图片描述

  9. SkipLast
    跳过后n项,由于不知道尽头在哪,所以弄了个n大小的队列,即可简单实现
    在这里插入图片描述

  10. Take
    拿前n项
    在这里插入图片描述

  11. TakeLast
    拿后n项
    在这里插入图片描述

项目代码