Pola Elegan dalam JavaScript Modern (Siklus Tim Sourour Bill)

Halo, Habr! Cukup guru guru JavaScript terkenal Bill Sourour pada saat itu menulis beberapa artikel tentang pola modern di JS. Sebagai bagian dari artikel ini, kami akan mencoba meninjau ide-ide yang dia bagikan. Bukannya itu beberapa patters unik, tapi saya harap artikel ini akan menemukan pembacanya. Artikel ini bukan "terjemahan" dari sudut pandang kebijakan Habr sejak itu Saya menggambarkan pikiran saya bahwa artikel Bill telah mengarahkan saya.

Rooro


Singkatan berarti Menerima objek, mengembalikan objek - mendapatkan objek, mengembalikan objek. Saya memberikan tautan ke artikel asli: tautan

Bill menulis bahwa ia menemukan cara untuk menulis fungsi di mana kebanyakan dari mereka hanya menerima satu parameter - objek dengan argumen fungsi. Mereka juga mengembalikan objek hasil. Bill terinspirasi oleh restrukturisasi ide ini (salah satu fitur ES6).

Bagi mereka yang tidak tahu tentang perusakan, saya akan memberikan penjelasan yang diperlukan selama cerita.

Bayangkan kita memiliki data pengguna yang berisi haknya untuk bagian tertentu dari aplikasi yang disajikan dalam objek data. Kami perlu menunjukkan informasi tertentu berdasarkan data ini. Untuk melakukan ini, kami dapat menawarkan implementasi berikut:

//   const user = { name: 'John Doe', login: 'john_doe', password: 12345, active: true, rules: { finance: true, analitics: true, hr: false } }; //   const users = [user]; //,     function findUsersByRule ( rule, withContactInfo, includeInactive) { //        active const filtredUsers= users.filter(item => includeInactive ? item.rules[rule] : item.active && item.rules[rule]); //  ()   ( )     withContactInfo return withContactInfo ? filtredUsers.reduce((acc, curr) => { acc[curr.id] = curr; return acc; }, {}) : filtredUsers.map(item => item.id) } //     findUsersByRule( 'finance', true, true) 

Dengan menggunakan kode di atas, kami akan mencapai hasil yang diinginkan. Namun, ada beberapa jebakan dalam menulis kode dengan cara ini.

Pertama, panggilan ke fungsi findUsersByRule sangat diragukan. Perhatikan betapa ambigu dua parameter terakhir. Apa yang terjadi jika aplikasi kita hampir tidak pernah membutuhkan informasi kontak (withContactInfo) tetapi hampir selalu membutuhkan pengguna yang tidak aktif (termasukInactive)? Kami akan selalu harus melewati nilai-nilai logis. Sekarang sementara deklarasi fungsi di sebelah panggilannya, ini tidak begitu menakutkan, tetapi bayangkan bahwa Anda melihat panggilan semacam itu di suatu tempat yang dibuat di modul lain. Anda harus mencari modul dengan deklarasi fungsi untuk memahami mengapa dua nilai logis dalam bentuk murni ditransfer ke sana.

Kedua, jika kita ingin membuat beberapa parameter wajib, kita harus menulis sesuatu seperti ini:

 function findUsersByRule ( role, withContactInfo, includeInactive) { if (!role) { throw Error(...) ; } //...  } 

Dalam hal ini, fungsi kami, selain tanggung jawab pencariannya, juga akan melakukan validasi, dan kami hanya ingin menemukan pengguna dengan parameter tertentu. Tentu saja, fungsi pencarian dapat mengambil fungsi validasi, tetapi kemudian daftar parameter input akan diperluas. Ini juga merupakan minus dari pola pengkodean seperti itu.

Destrukturisasi melibatkan memecah struktur yang kompleks menjadi bagian-bagian sederhana. Dalam JavaScript, struktur kompleks seperti itu biasanya berupa objek atau array. Menggunakan sintaksis penataan, Anda dapat mengekstrak fragmen kecil dari array atau objek. Sintaks ini dapat digunakan untuk mendeklarasikan variabel atau tujuannya. Anda juga dapat mengelola struktur bersarang menggunakan sintaksis dari penghancuran bersarang.

Menggunakan destructure, fungsi dari contoh kita sebelumnya akan terlihat seperti ini:

 function findUsersByRule ({ rule, withContactInfo, includeInactive}) { //    } findUsersByRule({ rule: 'finance', withContactInfo: true, includeInactive: true}) 

Harap perhatikan bahwa fungsi kami terlihat hampir identik, kecuali bahwa kami menempatkan tanda kurung di sekitar parameter kami. Alih-alih menerima tiga parameter berbeda, fungsi kami sekarang mengharapkan satu objek dengan properti: rule , withContactInfo dan includeInactive .

Ini jauh lebih ambigu, lebih mudah dibaca dan dimengerti. Selain itu, melewatkan atau urutan parameter kami yang lain tidak lagi menjadi masalah, karena sekarang parameter tersebut dinamai properti objek. Kita juga bisa menambahkan parameter baru dengan aman ke deklarasi fungsi. Selain itu, sejak Karena merusak menyalin nilai yang diteruskan, perubahan fungsi tidak akan memengaruhi yang asli.

Masalah dengan parameter yang diperlukan juga dapat diselesaikan dengan cara yang lebih elegan.

 function requiredParam (param) { const requiredParamError = new Error( `Required parameter, "${param}" is missing.` ) } function findUsersByRule ({ rule = requiredParam('rule'), withContactInfo, includeInactive} = {}) {...} 

Jika kita tidak melewatkan nilai aturan, maka fungsi yang dilewati secara default akan berfungsi, yang akan menghasilkan pengecualian.

Fungsi dalam JS hanya dapat mengembalikan satu nilai, sehingga Anda dapat menggunakan objek untuk mentransfer informasi lebih lanjut. Tentu saja, kami tidak selalu membutuhkan fungsi untuk mengembalikan banyak informasi, dalam beberapa kasus kami akan puas dengan pengembalian primitif, misalnya, findUserId secara alami akan mengembalikan satu pengidentifikasi dengan beberapa syarat.

Juga, pendekatan ini menyederhanakan komposisi fungsi. Memang, dengan komposisi, fungsi seharusnya hanya mengambil satu parameter. Pola RORO mematuhi kontrak yang sama.

Bill Sourour: β€œSeperti templat apa pun, RORO harus dilihat sebagai alat lain di kotak alat kami. "Kami menggunakannya di tempat yang menguntungkan, membuat daftar parameter lebih mudah dimengerti dan fleksibel, dan nilai pengembalian lebih ekspresif."

Pabrik es


Anda dapat menemukan artikel asli di tautan ini .

Menurut penulis, templat ini adalah fungsi yang membuat dan mengembalikan objek beku.

Bill berpikir. bahwa dalam beberapa situasi pola ini dapat menggantikan kelas ES6 yang biasa bagi kita. Misalnya, kami memiliki keranjang makanan tertentu di mana kami dapat menambah / menghapus produk.

Kelas ES6:

 // ShoppingCart.js class ShoppingCart { constructor({db}) { this.db = db } addProduct (product) { this.db.push(product) } empty () { this.db = [] } get products () { return Object .freeze([...this.db]) } removeProduct (id) { // remove a product } // other methods } // someOtherModule.js const db = [] const cart = new ShoppingCart({db}) cart.addProduct({ name: 'foo', price: 9.99 }) 

Objek yang dibuat menggunakan kata kunci new dapat berubah, yaitu kita dapat mengganti metode instance kelas.

 const db = [] const cart = new ShoppingCart({db}) cart.addProduct = () => 'nope!' //   JS  cart.addProduct({ name: 'foo', price: 9.99 }) // output: "nope!"     

Juga harus diingat bahwa kelas-kelas di JS diimplementasikan pada delegasi prototipe, oleh karena itu, kita dapat mengubah implementasi metode dalam prototipe kelas dan perubahan-perubahan ini akan mempengaruhi semua instance yang ada (saya membicarakan hal ini secara lebih rinci dalam artikel tentang OOP ).

 const cart = new ShoppingCart({db: []}) const other = new ShoppingCart({db: []}) ShoppingCart.prototype .addProduct = () => 'nope!' //     JS cart.addProduct({ name: 'foo', price: 9.99 }) // output: "nope!" other.addProduct({ name: 'bar', price: 8.88 }) // output: "nope!" 

Setuju, fitur tersebut dapat menyebabkan kita banyak masalah.

Masalah umum lainnya adalah menetapkan metode instance ke pengendali event.

 document .querySelector('#empty') .addEventListener( 'click', cart.empty ) 

Mengklik tombol tidak akan mengosongkan keranjang. Metode ini menetapkan properti baru ke tombol kami bernama db dan menyetel properti ini ke [] alih-alih memengaruhi db objek keranjang. Namun, tidak ada kesalahan di konsol, dan akal sehat Anda akan memberi tahu Anda bahwa kode harus bekerja, tetapi tidak.

Agar kode ini berfungsi, Anda harus menulis fungsi panah:

 document .querySelector("#empty") .addEventListener( "click", () => cart.empty() ) 

Atau ikat konteks dengan ikat:

 document .querySelector("#empty") .addEventListener( "click", cart.empty.bind(cart) ) 

Pabrik Es akan membantu kita menghindari perangkap ini.

 function makeShoppingCart({ db }) { return Object.freeze({ addProduct, empty, getProducts, removeProduct, // others }) function addProduct (product) { db.push(product) } function empty () { db = [] } function getProducts () { return Object .freeze([...db]) } function removeProduct (id) { // remove a product } // other functions } // someOtherModule.js const db = [] const cart = makeShoppingCart({ db }) cart.addProduct({ name: 'foo', price: 9.99 }) 

Fitur dari pola ini:

  • tidak perlu menggunakan kata kunci baru
  • tidak perlu mengikat ini
  • kereta sepenuhnya portabel
  • variabel lokal dapat dideklarasikan yang tidak akan terlihat dari luar

 function makeThing(spec) { const secret = 'shhh!' return Object.freeze({ doStuff }) function doStuff () { //    secret } } // secret    const thing = makeThing() thing.secret // undefined 

  • Pola mendukung warisan
  • membuat objek menggunakan Ice Factory lebih lambat dan membutuhkan lebih banyak memori daripada menggunakan kelas (Dalam banyak situasi, kita mungkin perlu kelas, jadi saya merekomendasikan artikel ini)
  • ini adalah fungsi umum yang dapat ditetapkan sebagai panggilan balik

Kesimpulan


Ketika kita berbicara tentang arsitektur perangkat lunak yang dikembangkan, kita harus selalu membuat kompromi yang nyaman. Tidak ada aturan dan batasan ketat dalam jalur ini, setiap situasi unik, jadi semakin banyak pola dalam gudang senjata kita, semakin besar kemungkinan kita akan memilih opsi arsitektur terbaik dalam situasi tertentu.

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


All Articles