关于反应式编程的另一篇文章。 而且只是不要将视线移到这条线上,用in弱的声音大声说出来-“那么,关于反应式编程,您还能告诉我什么……嗯?” 它与其他复写纸(有点像复写纸)有些不同,因此其中有些东西看起来似乎很奇怪甚至完全不合适,例如幽默。
无论您是内心了解反应式宣言,还是早上喝咖啡中是否有背压,或者用这些笔接触各种发布者和订阅者,或者编写良好的老式同步阻塞代码都没有关系。 或者也许只是最近,有人坦率地发布了关于光明的未来和高潮的报道(或者说是喷墨,这是翻译的微妙之处决定了一切),当然,从使用其中一个反应式库激发了您对这种新技术的兴趣。
会很有趣。
绝对的
因此,让我们想象一下我们完全疯了……尽管在这里出现这个词更多是出于政治上的正确性,因为认识作者的每个人都知道他整天坐在一间上锁的公寓里这一事实。 它在晚上工作,每天抽三个水烟,一周要出去两次。 通常碰巧会遇到您的治疗师,但有时原因却完全不同-宜家。
因此,我们采用了旧的同步但有效的代码:
int result = 1 + 2 * 4;
而且我们尝试对其进行重构,因为在我们看来似乎如此……尽管不行,但它太多了,没有人会在他们的正确想法中做到这一点。 没有人 在你正确的想法。
Mono<Integer> result = Flux.concat( Mono.just(1), Flux.concat(Mono.just(2), Mono.just(4)) .reduce((a, b) -> a * b)) .reduce((a, b) -> a + b); StepVerifier.create(result) .expectNext(9) .expectComplete() .verify();
但是,正如您可能已经注意到的,我做到了。 并请注意! 在这个示例中,我限制自己使用几个微服务,一个用于加法,另一个用于乘法,我必须通过rSocket
和kafka
进行通信。
顺便说一句,这是一个热身。 在开始之前,我想给出两个提示。 因此,第一个建议是:始终做好准备,以防您的同事可能是病态的精神病患者,带有一堆儿童综合体,这些综合体将以代码的形式(不是最好的质量)和非常优雅的体系结构解决方案出现。
秘诀二。 我决定将其保留在文章末尾。 但是,如果在看到所有内容之后,您决定不使用d%&@ a,那么您就知道在哪里寻找它。
更真实的例子
让我们来看一个更现实的例子。 例如...树木。 为什么是他们呢? 因为我们都将树木当作现实生活中的对象和数据结构。 这是我们周围世界上最重要的部分之一。 树木产生氧气,因此我们可以呼吸。
许多动物生活在树木上,或生活在这些物体堆积的地方(根据需要,这些地方被称为公园或森林),但是动物,甚至无家可归的人,也生活在最初是树木的纸板箱中。
顺便说一句,一个有趣的事实,自古以来,人类一直在从树木中提取人类生命中最常用的材料之一-木材。 试想一下,我们一生都插入车轮的所有木棍,同时也怪罪了其他人,他们都是木头做的。
我们有一棵树,这是一棵普通的,不起眼的树,生长在我们代码的草坪上,并且梦想有一天长大后变成BST:
public class TreeNode { public int val; public TreeNode left, right; public TreeNode(int val, TreeNode left, TreeNode right) { this.val = val; this.left = left; this.right = right; } public TreeNode(int val) { this(val, null, null); }
例如在星期一的早晨六点,在一个不眠之夜之后的某个时刻,一个程序员想到了使其具有反应性。 而他做到了。 因为他是一个工作的人。
public class TreeNode { public final Mono<Integer> value; public final Mono<TreeNode> left; public final Mono<TreeNode> right; public TreeNode(Mono<Integer> value, Mono<TreeNode> left, Mono<TreeNode> right) { this.value = value; this.left = left; this.right = right; } public TreeNode(int value, TreeNode left, TreeNode right) { this(Mono.just(value), Mono.justOrEmpty(left), Mono.justOrEmpty(right)); } public TreeNode(int value) { this(Mono.just(value), Mono.empty(), Mono.empty()); } public Flux<TreeNode> flux() { return Flux.concat( left.flatMapMany(TreeNode::flux), Mono.just(this), right.flatMapMany(TreeNode::flux) ); } @Override public String toString() { return flux() .flatMap(n -> n.value) .collectList() .map(Object::toString) .block(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TreeNode treeNode = (TreeNode) o; return Objects.equals(value.block(), treeNode.value.block()); } @Override public int hashCode() { return value.block(); }
您感到悬而未决的问题-“为什么?!”。 首先,它很漂亮,其次,它具有很高的可扩展性,因为它是异步的,而且对环境友好,Greta Tunberg不会一次坐上火车,紧紧绑着我们……你知道我的意思。 并且不要忘记,现在我们有了反向新闻。 顺便提一下,我提醒您,没有测试覆盖范围就无法进行重构。
public class TreeNodeTest { @Test public void testFlux() { TreeNode tree = new TreeNode(4, new TreeNode(2, new TreeNode(1), new TreeNode(3)), new TreeNode(7, new TreeNode(6), new TreeNode(9))); StepVerifier.create(tree.flux().flatMap(n -> n.value)) .expectNext(1, 2, 3, 4, 6, 7, 9) .expectComplete() .verify(); } }
太好了 已经开始,现在我们只需要在旧的,枯燥的同步树中重构三种简单的方法invert
, sumOfLeftLeaves
和searchBST
并添加一些测试。
反转
因此,我们已经有一个实现的invert
方法,但是不幸的是,它不是反应性的,看上去很难过,这种方法的唯一优点是易于阅读,而且很容易理解,如果我们将树反转,那么我们递归地遍历其所有节点,创建副本并交换其子级。 也就是说,在新节点的左子节点中,我们将反向子节点放到右子节点中,反之亦然。 这些检查在所有地方都是空的。
public TreeNode invert() { return invert(this); } private TreeNode invert(TreeNode root) { if (root == null) return null; TreeNode swap = new TreeNode(root.val); swap.right = invert(root.left); swap.left = invert(root.right); return swap; }
看看我们重写它时发生了什么,它以新的颜色显示,并且作为奖励,我们免费获得了零安全。
public Mono<TreeNode> invert() { return Mono.just(this) .map(n -> new TreeNode(n.value, n.right.flatMap(TreeNode::invert), n.left.flatMap(TreeNode::invert) )); } @Test public void testInvert() { TreeNode tree = new TreeNode(4, new TreeNode(2, new TreeNode(1), new TreeNode(3)), new TreeNode(7, new TreeNode(6), new TreeNode(9))); Flux<Integer> inverted = tree.invert() .flatMapMany(TreeNode::flux) .flatMap(n -> n.value); StepVerifier.create(inverted) .expectNext(9, 7, 6, 4, 3, 2, 1) .expectComplete() .verify(); }
sumOfLeftLeaves
通常,人们分为两种类型,第一类是从其名称中了解此方法将执行的操作的人,以及所有其他未离开房间多年且从未见过树的人。
我可以从我的心理治疗师的建议中帮助第二类人,他很可能能够制定治疗方案。 并告诉此方法的含义。 如果节点没有子节点,则视为leaf
如果节点从左指针left
在其父节点处增长,则视为节点。 这是以方法的名称编写的。
但是,为什么要言辞,让我们开始做下去,看看它是怎么回事...
public int sumOfLeftLeaves() { return sumOfLeftLeaves(false, this); } public int sumOfLeftLeaves(boolean left, TreeNode root) { if (root == null) return 0; if (root.left == null && root.right == null && left) return root.val; return sumOfLeftLeaves(true, root.left) + sumOfLeftLeaves(false, root.right); }
...以及它如何变成
public Mono<Integer> sumOfLeftLeaves() { return sumOfLeftLeaves(Mono.just(this), false) .flatMap(n -> n.value) .reduce(Integer::sum); } private Flux<TreeNode> sumOfLeftLeaves(Mono<TreeNode> node, boolean left) { return node .flux() .concatMap(n -> Flux.concat( sumOfLeftLeaves(n.left, true), Flux.first(n.left, n.right) .map(x -> new TreeNode(0)) .switchIfEmpty( Mono.just(n) ) .filter(x -> left), sumOfLeftLeaves(n.right, false) )); } @Test public void testSumOfLeftLeaves() { TreeNode tree = new TreeNode(3, new TreeNode(9, new TreeNode(11), null), new TreeNode(20, new TreeNode(15), new TreeNode(7)) ); StepVerifier.create(tree.sumOfLeftLeaves()) .expectNext(26) .expectComplete() .verify(); }
哇! 如此反应。 非常不同步。 太厉害了 因此没有安全性。 很大的背压。 如此可扩展...
顺便说一句,我们还有另外一种方法,我向您保证,我们将把所有这些力量导向正确的方向。
searchBST
每棵树都梦想成为二进制搜索树,这种方法将帮助我们搜索它。 这说明了一切,所以我立即展示出它是多么的可怕,以及它变得多么美好。
public TreeNode searchBST(int val) { return searchBST(this, val); } public TreeNode searchBST(TreeNode root, int val) { if (root == null) return null; if (val < root.val) return searchBST(root.left, val); else if (val > root.val) return searchBST(root.right, val); else return root; }
只是感觉到:
public Mono<TreeNode> testSearchBST(int val) { return searchBST(Mono.just(this), val); } private Mono<TreeNode> searchBST(Mono<TreeNode> root, int val) { return root.flatMap(node -> node.value .filter(v -> v > val) .flatMap(v -> searchBST(node.left, val)) .switchIfEmpty(node.value .filter(v -> v < val) .flatMap(v -> searchBST(node.right, val)) .switchIfEmpty(node.value .filter(v -> v == val) .flatMap(v -> root) ) )); } @Test public void searchBST() { TreeNode tree = new TreeNode(4, new TreeNode(2, new TreeNode(1), new TreeNode(3)), new TreeNode(7)); StepVerifier.create(tree.searchBST(3).flatMap(n -> n.value)) .expectNext(3) .expectComplete() .verify(); }
没什么可添加的。 这是反应式编程,这是最多的。
还没结束
当您做得很酷的事情时,很难停下来。 您很累,闭着眼睛,想睡觉,但是内心却感觉到巨大的动力。 您做对了所有事情,您会停下来,领会这种感觉,继续使世界变得更美好。
因此,在消除了树木之后,您可以毫不犹豫地浏览旧的,简单的关联列表。 他们的时间还没有过去,仍然有成千上万的技术面试人员检查他们的视线。 是时候做出反应了。
public class ListNode { public int val; public ListNode next; public ListNode(int val) { this.val = val; } public ListNode(int val, ListNode next) { this.val = val; this.next = next; } public static ListNode of(int... array) { if (array.length < 1) return null; ListNode head = new ListNode(array[0]); ListNode tail = head; for (int i = 1; i < array.length; i++) { ListNode next = new ListNode(array[i]); tail.next = next; tail = next; } return head; } public ListNode last() { if (next != null) return next.last(); return this; } } public class ListTestNode { public Mono<Boolean> hasCycle(ListNode head) { return Mono.justOrEmpty(head) .flatMapMany(node -> { Flux<ListNode> flux = Flux.generate(() -> head, (n, sink) -> { if (n == null) { sink.complete(); return null; } sink.next(n); return n.next; }); Flux<ListNode> fast = flux.skip(1); Flux<ListNode> slow = flux.flatMap(n -> Flux.just(n, n)); return fast.zipWith(slow); }) .any(objects -> objects.getT1() == objects.getT2()) .defaultIfEmpty(false); } @Test public void hasCycle() { StepVerifier.create(hasCycle(null)) .expectNext(false) .expectComplete() .verify(); ListNode withoutCycle = ListNode.of(1, 2, 3, 4, 5, 6); StepVerifier.create(hasCycle(withoutCycle)) .expectNext(false) .expectComplete() .verify(); ListNode withCycle = ListNode.of(1, 2, 3, 4, 5, 6); withCycle.last().next = withCycle.next.next; StepVerifier.create(hasCycle(withCycle)) .expectNext(true) .expectComplete() .verify(); }
既然我们谈到了技术面试的话题,那么我们就不要忘记检查托架的心爱的任务。 表演必须继续!
public Mono<Boolean> isValidParentheses(String s) { return Flux.range(0, s.length()) .map(s::charAt) .reduceWith(() -> "", (str, c) -> { if (c == '{' || c == '[' || c == '(') return str + c; char last = str.charAt(str.length() - 1); if (c == '}' && last != '{') return str; if (c == ']' && last != '[') return str; if (c == ')' && last != '(') return str; return str.substring(0, str.length() - 1); }) .map(String::isEmpty); } @Test public void testIsValidParentheses() { StepVerifier.create(isValidParentheses("()")) .expectNext(true) .expectComplete() .verify(); StepVerifier.create(isValidParentheses("()[]{}")) .expectNext(true) .expectComplete() .verify(); StepVerifier.create(isValidParentheses("{()[]()}")) .expectNext(true) .expectComplete() .verify(); StepVerifier.create(isValidParentheses("()")) .expectNext(true) .expectComplete() .verify(); StepVerifier.create(isValidParentheses("(]")) .expectNext(false) .expectComplete() .verify(); StepVerifier.create(isValidParentheses("([)]")) .expectNext(false) .expectComplete() .verify(); }
哇 如此一成不变。
技巧二
顺便说一句,正如我所承诺的,在本文的结尾是第二点建议: .
有时,它看起来至少很愚蠢且不切实际。