Reaktive Programmierung, lohnt es sich, alles fallen zu lassen und dem Traum entgegen zu eilen?

Ein weiterer Artikel über reaktive Programmierung. Und nur nicht mit den Augen auf diese Zeile rollen und mit leiser Stimme laut sprechen - "Nun, was können Sie mir noch über reaktives Programmieren sagen ... wie?" Es unterscheidet sich ein wenig von einigen anderen, die wie Kohlepapier geschrieben sind, sodass einige Dinge darin ... seltsam oder sogar völlig unangemessen erscheinen, wie sortierter Humor.


Es spielt keine Rolle, ob Sie das reaktive Manifest auswendig kennen, ob in Ihrem Morgenkaffee Rückstau herrscht, ob Sie mit diesen Stiften alle Arten von Verlagen und Abonnenten berühren oder guten alten synchronen, blockierenden Code schreiben. Oder vielleicht erst kürzlich, hat jemand mit seinem aufrichtigen Werbebericht über die strahlende Zukunft und den Streaming-Orgasmus (na ja, oder Inkjet, hier entscheiden die Feinheiten der Übersetzung alles) ein Interesse an der neuen Technologie in Ihren Augen geweckt, indem er eine der reaktiven Bibliotheken verwendet hat.


Es wird interessant sein.


Absolut


Stellen wir uns also vor, wir sind total verrückt ... Obwohl das Wort hier eher aus politischen Gründen verwendet wird, weil jeder, der den Autor kennt, sich der Tatsache bewusst ist, dass er den ganzen Tag in einer verschlossenen Wohnung sitzt. Es funktioniert nachts, raucht drei Wasserpfeifen pro Tag und geht ein paar Mal pro Woche nach draußen. Dies geschieht normalerweise mit Ihrem Therapeuten, aber manchmal ist der Grund völlig anders - IKEA.


Also nehmen wir den alten synchronen, aber funktionierenden Code:


int result = 1 + 2 * 4; 

Und wir versuchen es umzugestalten, weil es uns so vorkommt ... Obwohl nein, es ist zu viel, wird es niemand bei klarem Verstand tun. Niemand Bei klarem Verstand.


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

Aber wie Sie vielleicht bemerkt haben, habe ich es getan. Und bitte beachten Sie! Ich habe mich in diesem Beispiel rSocket kafka rSocket kafka , einen für die Addition und einen für die Multiplikation, die ich über rSocket und kafka kommunizieren kafka .


Übrigens war es ein Warm-up. Bevor wir beginnen, möchte ich zwei Tipps geben. Und so der erste Ratschlag: Seien Sie immer darauf vorbereitet, dass Ihre Kollegen wirklich kranke Psychopathen sein können, mit einer Reihe von Kinderkomplexen, die in Form eines Codes, nicht von bester Qualität, und sehr eleganten architektonischen Lösungen auftauchen.


Tipp zwei. Ich habe beschlossen, es am Ende des Artikels zu belassen. Aber wenn Sie nach allem, was Sie sehen, entscheiden, dass Sie sich von diesem d% & @ a fernhalten sollten, dann wissen Sie, wo Sie danach suchen müssen.


Echteres Beispiel


Nehmen wir ein etwas realistischeres Beispiel. Zum Beispiel ... Bäume. Warum genau sie? Weil wir alle Bäume sowohl als Objekt im wirklichen Leben als auch als Datenstruktur lieben. Dies ist einer der wichtigsten Teile der Welt um uns herum. Bäume produzieren Sauerstoff, damit wir atmen können.


Viele Tiere leben auf Bäumen oder an Orten, an denen sich diese Gegenstände ansammeln (diese Orte werden abhängig von Park oder Wald genannt), aber Tiere, sogar Obdachlose, leben in Pappkartons, die ursprünglich Bäume waren.


Übrigens ist es eine interessante Tatsache, dass die Menschheit seit der Antike eines der am häufigsten verwendeten Materialien im menschlichen Leben - Holz - aus den Bäumen gewinnt. Stellen Sie sich vor, all diese Stöcke, die wir unser ganzes Leben lang in unsere Räder stecken und gleichzeitig alle anderen beschuldigen, auch aus Holz.


Wir haben einen Baum, einen gewöhnlichen, unscheinbaren Baum, der auf dem Rasen unseres Codes gewachsen ist und eines Tages, wenn er erwachsen wird, in BST verwandelt wurde:


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

Und irgendwann, zum Beispiel am Montag um sechs Uhr morgens, hatte ein Programmierer nach einer schlaflosen Nacht die Idee, sie reaktiv zu machen. Und er hat es geschafft. Weil er ein Mann der Arbeit ist.


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

Sie spüren die Frage in der Luft hängen - "Warum?!". Erstens ist es wunderschön und zweitens ist es sehr skalierbar, da es asynchron ist, außerdem ist es umweltfreundlich und Greta Tunberg wird nicht einmal mit einem riesigen Umschnalldildo zu uns kommen ... Sie wissen, was ich meine. Und vergessen Sie nicht, dass wir jetzt Druck zurück haben. Übrigens erinnere ich Sie daran, dass kein Refactoring ohne Testabdeckung durchgeführt werden kann.


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

Großartig. Der Anfang ist gemacht, jetzt müssen wir nur noch drei einfache invert , sumOfLeftLeaves und searchBST im alten, langweiligen, synchronen Baum sumOfLeftLeaves und einige Tests hinzufügen.


Invertieren


Wir haben also bereits eine invert Methode implementiert, aber leider ist sie nicht reaktiv. Sehen Sie, wie traurig sie aussieht. Das einzige Plus dieser Methode ist, dass sie leicht zu lesen ist und dass es leicht zu verstehen ist, wenn wir den Baum invertieren Wir gehen rekursiv alle Knoten durch, erstellen Kopien und tauschen die untergeordneten Knoten aus. Das heißt, im linken Kind des neuen Knotens setzen wir den invertierten Knoten vom rechten Kind und umgekehrt. Und überall sind diese Überprüfungen null.


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

Schauen Sie, was passiert ist, als wir es umgeschrieben haben, es mit neuen Farben gespielt hat und als Bonus haben wir null Sicherheit gratis bekommen.


 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


Normalerweise werden Menschen in zwei Typen eingeteilt, der erste Typ ist derjenige, der versteht, was diese Methode aus ihrem Namen macht, und alle anderen Menschen, die ihr Zimmer seit Jahren nicht verlassen haben und noch nie Bäume gesehen haben.


Ich kann dem zweiten Typ von Menschen mit Rat von meinem Psychotherapeuten helfen, der wahrscheinlich in der Lage ist, ein Behandlungsschema zu erstellen. Und erklären Sie die Bedeutung dieser Methode. Ein Knoten wird als leaf wenn er keine leaf Elemente hat. Er wird verlassen, wenn er an seinem übergeordneten Element vom linken Zeiger nach left wächst. Dies steht im Namen der Methode.


Aber warum Worte, lassen Sie uns zur Sache kommen, sehen, wie es war ...


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

... und wie es wurde


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

Wow! So reaktiv. Sehr asynchron. Zu genial Also null Sicherheit. Viel Gegendruck. So skalierbar ...


Übrigens haben wir noch eine Methode, bei der wir, wie ich Ihnen verspreche, all diese Kraft in die richtige Richtung lenken werden.


searchBST


Jeder Baum träumt davon, ein binärer Suchbaum zu werden, und diese Methode hilft uns bei der Suche danach. Das sagt alles, also zeige ich sofort, wie schrecklich es war und wie wunderbar es wurde.


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

Fühle es einfach:


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

Nichts mehr hinzuzufügen. Das ist reaktive Programmierung, es ist die meiste.


Noch nicht vorbei


Wenn du wirklich coole Dinge tust, ist es sehr schwierig aufzuhören. Sie sind müde, Ihre Augen sind geschlossen, Sie möchten schlafen, aber in Ihrem Inneren spüren Sie einen riesigen Strom von Motivation. Wenn Sie alles richtig machen, können Sie nicht aufhören, dieses Gefühl einfangen und die Welt weiter verbessern.


Nachdem Sie die Bäume beseitigt haben, gehen Sie ohne zu zögern zu den guten alten, einfach verbundenen Listen über. Ihre Zeit ist noch nicht abgelaufen, dennoch prüfen Tausende von Personen bei technischen Interviews, ob sie fixiert sind. Es ist Zeit, es reaktiv zu machen.


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

Da wir das Thema technische Interviews angesprochen haben, sollten wir nicht die geliebte Aufgabe vergessen, das Gehäuse von Klammern zu überprüfen. Die Show muss weitergehen!


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

Wow So unveränderlich ... Sehr reaktiv.


Tipp Nummer zwei


Übrigens, wie ich versprochen habe, steht am Ende des Artikels der Rat Nummer zwei: . Manchmal sieht es zumindest albern und unpraktisch aus.

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


All Articles