Pemrograman reaktif, apakah itu layak untuk menjatuhkan segalanya dan bergegas menuju mimpi

Artikel lain tentang pemrograman reaktif. Dan jangan memutar mata Anda pada baris ini dan berbicara dengan suara lantang - "Nah, apa lagi yang bisa Anda ceritakan tentang pemrograman reaktif ... eh?" Ini sedikit berbeda dari yang lain, ditulis seperti kertas karbon, jadi beberapa hal di dalamnya mungkin tampak ... aneh atau bahkan sama sekali tidak pantas, seperti humor yang disortir.


Sama sekali tidak masalah apakah Anda mengetahui manifesto reaktif dengan hati, apakah ada tekanan balik dalam kopi pagi Anda, jika Anda menyentuh semua jenis penerbit dan pelanggan dengan pena ini, atau Anda menulis kode sinkron dan blokir yang baik. Atau mungkin hanya baru-baru ini, seseorang dengan laporan iklannya yang jujur ​​tentang masa depan yang cerah dan orgasme yang mengalir (baik, atau inkjet, di sini seluk-beluk terjemahan menentukan segalanya), tentu saja, dari menggunakan salah satu perpustakaan reaktif, memicu minat pada teknologi baru di mata Anda.


Itu akan menarik.


Mutlak


Jadi, mari kita bayangkan bahwa kita benar-benar gila ... Meskipun kata itu akan disajikan di sini lebih untuk kebenaran politik, karena semua orang yang mengenal penulis menyadari fakta bahwa dia duduk sepanjang hari di apartemen yang terkunci. Ini bekerja pada malam hari, merokok tiga hookah sehari, dan pergi keluar beberapa kali seminggu. Ini biasanya terjadi untuk bertemu dengan terapis Anda, tetapi kadang-kadang alasannya sangat berbeda - IKEA.


Jadi, kami mengambil kode lama yang sinkron, tetapi berfungsi:


int result = 1 + 2 * 4; 

Dan kami mencoba untuk menolaknya, karena tampaknya bagi kami ... Meskipun tidak, itu terlalu banyak, tidak ada yang akan melakukannya dalam pikiran mereka. Tidak ada Di pikiran kanan Anda.


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

Tapi seperti yang mungkin Anda perhatikan, saya melakukannya. Dan harap diperhatikan! Saya menahan diri dari menggunakan beberapa layanan mikro dalam contoh ini, satu untuk penambahan dan satu untuk perkalian, yang harus saya komunikasikan melalui rSocket dan kafka .


Ngomong-ngomong, itu adalah pemanasan. Sebelum kita mulai, saya ingin memberikan dua tips. Jadi, saran pertama: selalu siap untuk kenyataan bahwa kolega Anda dapat menjadi psikopat yang benar-benar sakit dengan sekelompok kompleks anak-anak yang akan muncul dalam bentuk kode, bukan kualitas terbaik, dan solusi arsitektur yang sangat elegan.


Kiat kedua. Saya memutuskan untuk meninggalkannya di akhir artikel. Tetapi jika setelah semua yang Anda lihat, Anda memutuskan bahwa Anda harus menjauh dari ini% & @ a, maka Anda tahu di mana mencarinya.


Contoh yang lebih nyata


Mari kita ambil contoh yang sedikit lebih realistis. Misalnya ... pohon. Kenapa tepatnya mereka? Karena kita semua suka pohon baik sebagai objek dalam kehidupan nyata maupun sebagai struktur data. Ini adalah salah satu bagian terpenting dunia di sekitar kita. Pohon menghasilkan oksigen sehingga kita bisa bernafas.


Banyak hewan hidup di pohon, atau di tempat-tempat akumulasi benda-benda ini (tempat-tempat ini disebut taman atau hutan, tergantung pada), tetapi hewan, bahkan orang-orang tunawisma, hidup dalam kotak kardus yang awalnya pohon.


Ngomong-ngomong, fakta yang menarik, sejak zaman kuno, umat manusia telah mengekstraksi dari pohon salah satu bahan yang paling umum digunakan dalam kehidupan manusia - kayu. Bayangkan saja, semua tongkat yang kita masukkan ke roda kita sepanjang hidup kita, dan pada saat yang sama menyalahkan orang lain, juga terbuat dari kayu.


Kami memiliki pohon, pohon biasa, yang biasa-biasa saja yang tumbuh di halaman kode kami dan bermimpi suatu hari ketika tumbuh, berubah menjadi 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(); } } 

Dan pada satu titik, misalnya, pada hari Senin pukul enam pagi, setelah malam tanpa tidur, seorang programmer punya ide untuk membuatnya reaktif. Dan dia melakukannya. Karena dia adalah orang yang bekerja.


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

Anda merasakan pertanyaan yang menggantung di udara - “Mengapa?!”. Pertama, itu indah, dan kedua sangat terukur, karena asinkron, juga ramah lingkungan dan Greta Tunberg tidak akan datang kepada kita sekali pun di kereta dengan tali pengikat besar ... Anda tahu maksud saya. Dan jangan lupa bahwa sekarang kita telah menekan kembali. Dan omong-omong, saya mengingatkan Anda bahwa tidak ada refactoring yang dapat dilakukan tanpa cakupan tes.


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

Bagus Awal telah dibuat, sekarang kita hanya perlu sumOfLeftLeaves tiga metode sederhana invert , sumOfLeftLeaves dan searchBST di pohon tua, membosankan, sinkron dan menambahkan beberapa tes.


Balikkan


Jadi, kami sudah memiliki metode invert diimplementasikan, tetapi, sayangnya, itu tidak reaktif, lihat betapa sedihnya tampilannya, satu-satunya plus dari metode ini adalah mudah dibaca dan mudah dipahami bahwa jika kita membalikkan pohon, maka kami secara rekursif melewati semua simpulnya, membuat salinan dan menukar anak-anaknya. Yaitu, di anak kiri dari simpul baru, kami menempatkan simpul terbalik dari anak kanan dan sebaliknya. Dan di mana-mana cek ini batal.


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

Lihat apa yang terjadi ketika kami menulis ulang, itu dimainkan dengan warna baru, dan sebagai bonus, kami mendapat nol keamanan gratis.


 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


Biasanya orang dibagi menjadi dua jenis, tipe pertama adalah mereka yang mengerti apa yang akan dilakukan metode ini dari namanya, dan semua orang lain yang tidak meninggalkan kamar mereka selama bertahun-tahun dan tidak pernah melihat pohon.


Saya dapat membantu orang jenis kedua dengan saran dari psikoterapis saya, yang kemungkinan akan dapat menyusun rejimen pengobatan. Dan ceritakan arti dari metode ini. Suatu simpul dianggap sebagai leaf jika tidak memiliki anak, dan pergi jika tumbuh pada induknya dari penunjuk left . Ini ditulis atas nama metode.


Tapi mengapa kata-kata, mari kita ke bisnis, lihat bagaimana itu ...


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

... dan bagaimana jadinya


  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! Sangat reaktif. Banyak async. Terlalu mengagumkan Jadi keamanan nol. Banyak tekanan balik. Sangat scalable ...


Ngomong-ngomong, kita masih memiliki satu metode lagi, di mana, aku berjanji, kita akan mengarahkan semua kekuatan ini ke arah yang benar.


searchBST


Setiap pohon bermimpi menjadi pohon pencarian biner, dan metode ini akan membantu kita dalam mencarinya. Ini mengatakan itu semua, jadi saya segera menunjukkan betapa mengerikannya itu, dan betapa indahnya jadinya.


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

Rasakan saja:


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

Tidak ada lagi yang ditambahkan. Ini adalah pemrograman reaktif, ini adalah yang paling.


Belum berakhir


Ketika Anda melakukan hal-hal yang sangat keren, sangat sulit untuk berhenti. Anda lelah, mata Anda tertutup, Anda ingin tidur, tetapi di dalam Anda merasakan aliran motivasi yang sangat besar. Anda melakukan segalanya dengan benar, Anda tidak bisa berhenti, menangkap perasaan ini, terus membuat dunia menjadi tempat yang lebih baik.


Karena itu, setelah menyingkirkan pepohonan, Anda, tanpa ragu, melanjutkan ke daftar lama yang baik dan terhubung. Waktu mereka belum berlalu, masih ribuan orang di wawancara teknis memeriksa mereka untuk fiksasi. Sudah waktunya untuk melakukannya secara reaktif.


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

Karena kita menyentuh topik wawancara teknis, kita tidak boleh melupakan tugas tercinta untuk memeriksa kurung kurung. Pertunjukan harus berlanjut!


  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 Sangat abadi ... Banyak yang reaktif.


Kiat nomor dua


Ngomong-ngomong, seperti yang saya janjikan, di akhir artikel adalah saran nomor dua: . Kadang-kadang itu akan terlihat setidaknya konyol dan tidak praktis.

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


All Articles