Programação reativa, vale a pena largar tudo e correr em direção ao sonho

Outro artigo sobre programação reativa. E simplesmente não revire os olhos nesta linha e fale em voz alta com uma voz lânguida - "Bem, o que mais você pode me dizer sobre programação reativa ... eh?" É um pouco diferente de muitos outros, escritos como se fossem uma cópia carbono, então algumas coisas nela podem parecer ... estranhas ou até mesmo completamente inapropriadas, como humor organizado.


Não importa se você conhece o manifesto reativo de cor, se há contrapressão em seu café da manhã, se toca em todos os tipos de editores e assinantes com essas canetas ou se escreve um bom e velho código síncrono de bloqueio. Ou talvez apenas recentemente, alguém com seu relatório de publicidade franca sobre o futuro brilhante e o orgasmo de streaming (bem, ou jato de tinta, aqui as sutilezas da tradução decidem tudo), é claro, ao usar uma das bibliotecas reativas, despertou interesse pela nova tecnologia em seus olhos.


Vai ser interessante.


Absoluto


E então, vamos imaginar que somos completamente loucos ... Embora a palavra seja apresentada aqui mais por correção política, porque todos que conhecem o autor sabem que ele fica o dia inteiro em um apartamento trancado. Trabalha à noite, fuma três cachimbos de água por dia e sai duas vezes por semana. Isso geralmente acontece com o seu terapeuta, mas às vezes o motivo é completamente diferente - a IKEA.


E assim, pegamos o código síncrono antigo, mas funcional:


int result = 1 + 2 * 4; 

E tentamos refatorá-lo, porque parece-nos ... Embora não, seja demais, ninguém fará isso em sã consciência. Ninguem No seu perfeito juízo.


 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(); 

Mas como você deve ter notado, eu fiz. E por favor note! Eu me contive em usar alguns microsserviços neste exemplo, um para adição e outro para multiplicação, com os quais eu precisaria me comunicar através do rSocket e do kafka .


A propósito, foi um aquecimento. Antes de começar, gostaria de dar duas dicas. E assim, o primeiro conselho: esteja sempre preparado para o fato de seus colegas serem psicopatas realmente doentes com um monte de complexos infantis que aparecerão na forma de um código, não da melhor qualidade e soluções arquitetônicas muito elegantes.


Dica dois. Decidi deixá-lo no final do artigo. Mas se depois de tudo o que vê, você decide que deve ficar longe desses d% & @ a, então você sabe onde procurar.


Exemplo mais real


Vamos dar um exemplo um pouco mais realista. Por exemplo ... árvores. Por que exatamente eles? Porque todos nós amamos as árvores como um objeto na vida real e como uma estrutura de dados. Esta é uma das partes mais importantes do mundo ao nosso redor. As árvores produzem oxigênio para que possamos respirar.


Muitos animais vivem em árvores ou em locais de acúmulo desses objetos (esses lugares são chamados de parque ou floresta, dependendo), mas os animais, mesmo os sem-teto, vivem em caixas de papelão que eram originalmente árvores.


Aliás, um fato interessante, desde a antiguidade, a humanidade extrai das árvores um dos materiais mais usados ​​na vida humana - a madeira. Imaginem, todos aqueles paus que inserimos em nossas rodas a vida toda e, ao mesmo tempo, culpar todos os outros, também de madeira.


Temos uma árvore, uma árvore comum e normal que cresceu no gramado do nosso código e sonhou um dia quando crescer, se transformar em 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); } //       TreeNode invert() { // ... } int sumOfLeftLeaves() { // ... } TreeNode searchBST(int val) { // .. } public List<Integer> toList() { List<Integer> list = new ArrayList<>(); if (left != null) list.addAll(left.toList()); list.add(val); if (right != null) list.addAll(right.toList()); return list; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TreeNode treeNode = (TreeNode) o; return val == treeNode.val; } @Override public int hashCode() { return val; } @Override public String toString() { return toList().toString(); } } 

E um bom momento, por exemplo, na segunda-feira às seis da manhã, depois de uma noite sem dormir, um programador teve a idéia de torná-lo reativo. E ele fez isso. Porque ele é um homem de trabalho.


 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(); } 

Você sente a pergunta pairando no ar - "Por quê?!". Em primeiro lugar, é bonito e, em segundo lugar, é muito escalável, uma vez que é assíncrono, também é seguro para o meio ambiente e Greta Tunberg não virá a nós uma vez em um trem com uma enorme cinta ... você entende o que quero dizer. E não se esqueça que agora temos contrapressão. A propósito, lembro que nenhuma refatoração pode ser realizada sem a cobertura do teste.


 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(); } } 

Ótimo. O começo já foi feito, agora precisamos refatorar três métodos simples de invert , sumOfLeftLeaves e searchBST na searchBST antiga, chata e síncrona e adicionar alguns testes.


Inverter


E assim, já temos um método invert implementado, mas, infelizmente, não é reativo, veja como parece triste, a única vantagem deste método é que é fácil de ler e é fácil entender que, se invertermos a árvore, recursivamente percorremos todos os seus nós, criamos cópias e trocamos seus filhos. Ou seja, no filho esquerdo do novo nó, colocamos o nó invertido do filho direito e vice-versa. E em todos os lugares essas verificações são nulas.


  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; } 

Veja o que aconteceu quando a reescrevemos, tocou com novas cores e, como bônus, obtivemos segurança nula gratuitamente.


 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


Geralmente, as pessoas são divididas em dois tipos, o primeiro é aquele que entende o que esse método fará com o nome e todas as outras pessoas que não deixam o quarto há anos e nunca viram árvores.


Posso ajudar o segundo tipo de pessoas com aconselhamento do meu psicoterapeuta, que provavelmente poderá elaborar um regime de tratamento. E diga o significado deste método. Um nó é considerado uma leaf se não tiver filhos e será deixado se crescer no pai, a partir do ponteiro left . Isso está escrito no nome do método.


Mas por que palavras, vamos direto ao assunto, ver como foi ...


  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); } 

... e como se tornou


  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(); } 

Uau! Tão reativo. Muito assíncrono. Muito legal. Segurança nula. Muita contrapressão. Tão escalável ...


A propósito, ainda temos mais um método, no qual, prometo, direcionaremos todo esse poder para a direção certa.


searchBST


Cada árvore sonha em se tornar uma árvore de pesquisa binária, e esse método nos ajudará a procurá-la. Isso já diz tudo, então eu imediatamente mostro como foi terrível e como ficou maravilhoso.


  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; } 

Apenas sinta:


  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(); } 

Nada mais a acrescentar. Esta é a programação reativa, é a mais.


Ainda não acabou


Quando você está fazendo coisas realmente legais, é muito difícil parar. Você está cansado, com os olhos fechados, quer dormir, mas por dentro sente uma enorme corrente de motivação. Você faz tudo certo, não pode parar, perceber esse sentimento, continuar a fazer do mundo um lugar melhor.


Portanto, tendo eliminado as árvores, você, sem hesitação, prossegue para as boas e velhas listas simplesmente conectadas. Seu tempo ainda não passou, ainda milhares de pessoas em entrevistas técnicas os verificam quanto à fixação. É hora de fazê-lo reativamente.


 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(); } 

Uma vez que abordamos o tópico das entrevistas técnicas, não devemos esquecer a amada tarefa de verificar o compartimento dos colchetes. O show deve continuar!


  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(); } 

Uau. Tão imutável ... Muito reativo.


Dica número dois


A propósito, como prometi, no final do artigo está o conselho número dois: . Às vezes, parecerá pelo menos tolo e impraticável.

Source: https://habr.com/ru/post/pt481716/


All Articles