Salah satu prinsip terpenting pemrograman berorientasi objek – membatasi antarmuka internal dari antarmuka eksternal.
Itu adalah praktik yang “harus” dalam mengembangkan sesuatu yang lebih kompleks daripada aplikasi “hello world”.
Untuk memahami hal ini, mari kita melepaskan diri dari pembangunan dan mengalihkan pandangan kita ke dunia nyata.
Biasanya perangkat yang kami gunakan cukup kompleks. Namun membatasi antarmuka internal dari antarmuka eksternal memungkinkan untuk menggunakannya tanpa masalah.
Misalnya saja mesin kopi. Sederhana dari luar: sebuah tombol, sebuah layar, beberapa lubang…Dan, tentu saja, hasilnya – kopi yang nikmat! :)
Tapi di dalam… (gambar dari manual perbaikan)
Banyak detail. Tapi kita bisa menggunakannya tanpa mengetahui apapun.
Mesin kopi cukup bisa diandalkan bukan? Kita dapat menggunakannya selama bertahun-tahun, dan hanya jika terjadi kesalahan – bawalah untuk diperbaiki.
Rahasia keandalan dan kesederhanaan mesin kopi – semua detail disetel dengan baik dan tersembunyi di dalamnya.
Jika kita melepas penutup pelindung dari mesin kopi, maka penggunaannya akan jauh lebih rumit (dimana menekannya?), dan berbahaya (bisa menyetrum).
Seperti yang akan kita lihat, dalam pemrograman, objek seperti mesin kopi.
Namun untuk menyembunyikan detail di dalamnya, kami tidak akan menggunakan penutup pelindung, melainkan sintaksis khusus dari bahasa dan konvensi.
Dalam pemrograman berorientasi objek, properti dan metode dibagi menjadi dua kelompok:
Antarmuka internal – metode dan properti, dapat diakses dari metode lain di kelas, tetapi tidak dari luar.
Antarmuka eksternal – metode dan properti, dapat diakses juga dari luar kelas.
Jika kita melanjutkan analogi dengan mesin kopi – yang tersembunyi di dalamnya: tabung ketel, elemen pemanas, dan sebagainya – adalah antarmuka internalnya.
Antarmuka internal digunakan agar objek dapat berfungsi, detailnya menggunakan satu sama lain. Misalnya, tabung ketel dipasang pada elemen pemanas.
Namun dari luar mesin kopi ditutup dengan penutup pelindung, sehingga tidak ada yang bisa menjangkaunya. Detailnya tersembunyi dan tidak dapat diakses. Kita dapat menggunakan fitur-fiturnya melalui antarmuka eksternal.
Jadi, yang kita perlukan untuk menggunakan suatu objek adalah mengetahui antarmuka eksternalnya. Kita mungkin sama sekali tidak menyadari cara kerjanya di dalam, dan itu bagus.
Itu tadi perkenalan umum.
Dalam JavaScript, ada dua jenis bidang objek (properti dan metode):
Publik: dapat diakses dari mana saja. Mereka terdiri dari antarmuka eksternal. Hingga saat ini kami hanya menggunakan properti dan metode publik.
Pribadi: hanya dapat diakses dari dalam kelas. Ini untuk antarmuka internal.
Dalam banyak bahasa lain, terdapat juga bidang yang “dilindungi”: hanya dapat diakses dari dalam kelas dan mereka yang memperluasnya (seperti privat, namun ditambah akses dari kelas yang mewarisi). Mereka juga berguna untuk antarmuka internal. Dalam arti tertentu, mereka lebih tersebar luas daripada yang privat, karena kita biasanya ingin kelas yang mewarisi dapat mengaksesnya.
Bidang yang dilindungi tidak diterapkan dalam JavaScript pada tingkat bahasa, tetapi dalam praktiknya bidang tersebut sangat nyaman, sehingga dapat ditiru.
Sekarang kita akan membuat mesin kopi dalam JavaScript dengan semua jenis properti ini. Mesin kopi memiliki banyak detail, kami tidak akan memodelkannya agar tetap sederhana (walaupun kami bisa).
Mari kita membuat kelas mesin kopi sederhana terlebih dahulu:
kelas Mesin Kopi { Jumlah air = 0; // jumlah air di dalamnya konstruktor(kekuatan) { this.power = kekuatan; alert( `Membuat mesin kopi, power: ${power}` ); } } // membuat mesin kopi biarkan coffeeMachine = CoffeeMachine baru (100); // tambahkan air coffeeMachine.waterAmount = 200;
Saat ini properti waterAmount
dan power
bersifat publik. Kita dapat dengan mudah mendapatkan/mengaturnya dari luar ke nilai apa pun.
Mari kita ubah properti waterAmount
menjadi protected agar memiliki kontrol lebih besar terhadapnya. Misalnya, kami tidak ingin siapa pun menetapkannya di bawah nol.
Properti yang dilindungi biasanya diawali dengan garis bawah _
.
Hal ini tidak diterapkan pada level bahasa, namun terdapat kesepakatan umum di antara pemrogram bahwa properti dan metode tersebut tidak boleh diakses dari luar.
Jadi properti kita akan disebut _waterAmount
:
kelas Mesin Kopi { _Jumlah air = 0; atur Jumlah Air(nilai) { jika (nilai < 0) { nilai = 0; } this._waterAmount = nilai; } dapatkan Jumlah Air() { kembalikan ini._waterAmount; } konstruktor(kekuatan) { this._power = kekuatan; } } // membuat mesin kopi biarkan coffeeMachine = CoffeeMachine baru (100); // tambahkan air coffeeMachine.waterAmount = -10; // _waterAmount akan menjadi 0, bukan -10
Sekarang akses sudah terkendali, sehingga pengaturan jumlah air di bawah nol menjadi tidak mungkin.
Untuk properti power
, mari kita jadikan read-only. Terkadang suatu properti harus disetel pada waktu pembuatan saja, dan kemudian tidak pernah diubah.
Hal itulah yang terjadi pada mesin kopi: tenaga tidak pernah berubah.
Untuk melakukannya, kita hanya perlu membuat pengambil, bukan penyetel:
kelas Mesin Kopi { // ... konstruktor(kekuatan) { this._power = kekuatan; } dapatkan kekuatan() { kembalikan this._power; } } // membuat mesin kopi biarkan coffeeMachine = CoffeeMachine baru (100); alert(`Daya adalah: ${coffeeMachine.power}W`); // Daya adalah: 100W coffeeMachine.power = 25; // Kesalahan (tidak ada penyetel)
Fungsi pengambil/penyetel
Di sini kami menggunakan sintaks pengambil/penyetel.
Namun seringkali fungsi get.../set...
lebih disukai, seperti ini:
kelas Mesin Kopi { _Jumlah air = 0; setJumlah Air(nilai) { jika (nilai < 0) nilai = 0; this._waterAmount = nilai; } dapatkanJumlah Air() { kembalikan ini._waterAmount; } } Mesin Kopi baru().setWaterAmount(100);
Kelihatannya sedikit lebih panjang, namun fungsinya lebih fleksibel. Mereka dapat menerima banyak argumen (walaupun kita tidak membutuhkannya saat ini).
Di sisi lain, sintaks get/set lebih pendek, jadi pada akhirnya tidak ada aturan ketat, terserah Anda yang memutuskan.
Bidang yang dilindungi diwariskan
Jika kita mewarisi class MegaMachine extends CoffeeMachine
, maka tidak ada yang menghalangi kita untuk mengakses this._waterAmount
atau this._power
dari metode kelas baru.
Jadi lahan yang dilindungi secara alami dapat diwariskan. Berbeda dengan yang pribadi yang akan kita lihat di bawah.
Tambahan baru-baru ini
Ini adalah tambahan terbaru pada bahasa ini. Tidak didukung di mesin JavaScript, atau belum didukung sebagian, memerlukan polyfilling.
Ada proposal JavaScript yang sudah selesai, hampir dalam standar, yang menyediakan dukungan tingkat bahasa untuk properti dan metode pribadi.
Prajurit harus dimulai dengan #
. Mereka hanya dapat diakses dari dalam kelas.
Misalnya, inilah properti pribadi #waterLimit
dan metode pribadi pengecekan air #fixWaterAmount
:
kelas Mesin Kopi { #Batas Air = 200; #memperbaikiJumlah Air(nilai) { jika (nilai < 0) mengembalikan 0; if (nilai > ini.#waterLimit) kembalikan ini.#waterLimit; } setJumlah Air(nilai) { this.#waterLimit = ini.#fixWaterAmount(nilai); } } biarkan coffeeMachine = new CoffeeMachine(); // tidak dapat mengakses private dari luar kelas coffeeMachine.#fixWaterAmount(123); // Kesalahan coffeeMachine.#waterLimit = 1000; // Kesalahan
Pada tingkat bahasa, #
adalah tanda khusus bahwa bidang tersebut bersifat pribadi. Kami tidak dapat mengaksesnya dari luar atau dari kelas yang mewarisi.
Bidang privat tidak bertentangan dengan bidang publik. Kita dapat memiliki kolom #waterAmount
pribadi dan kolom waterAmount
publik secara bersamaan.
Sebagai contoh, mari kita jadikan waterAmount
sebagai pengakses untuk #waterAmount
:
kelas Mesin Kopi { #Jumlah Air = 0; dapatkan Jumlah Air() { kembalikan ini.#waterAmount; } atur Jumlah Air(nilai) { jika (nilai < 0) nilai = 0; this.#Jumlah Air = nilai; } } biarkan mesin = Mesin Kopi baru(); mesin.airJumlah = 100; alert(mesin.#waterAmount); // Kesalahan
Tidak seperti bidang yang dilindungi, bidang privat diberlakukan oleh bahasa itu sendiri. Itu hal yang bagus.
Namun jika kita mewarisi CoffeeMachine
, maka kita tidak akan memiliki akses langsung ke #waterAmount
. Kita harus mengandalkan pengambil/penyetel waterAmount
:
kelas MegaCoffeeMachine memperluas CoffeeMachine { metode() { waspada( ini.#Jumlah Air ); // Kesalahan: hanya dapat mengakses dari CoffeeMachine } }
Dalam banyak skenario, pembatasan seperti ini terlalu parah. Jika kami memperluas CoffeeMachine
, kami mungkin memiliki alasan yang sah untuk mengakses internalnya. Itu sebabnya bidang yang dilindungi lebih sering digunakan, meskipun tidak didukung oleh sintaksis bahasa.
Bidang pribadi tidak tersedia karena ini[nama]
Bidang privat itu istimewa.
Seperti yang kita ketahui, biasanya kita dapat mengakses field menggunakan this[name]
:
Pengguna kelas { ... ucapkan Hai() { biarkan fieldName = "nama"; alert(`Halo, ${ini[namabidang]}`); } }
Dengan bidang pribadi itu tidak mungkin: this['#name']
tidak berfungsi. Itu adalah batasan sintaksis untuk memastikan privasi.
Dalam istilah OOP, pembatasan antarmuka internal dari antarmuka eksternal disebut enkapsulasi.
Ini memberikan manfaat berikut:
Perlindungan bagi pengguna, agar tidak menembak kaki sendiri
Bayangkan, ada tim pengembang yang menggunakan mesin kopi. Itu dibuat oleh perusahaan “Mesin Kopi Terbaik”, dan berfungsi dengan baik, tetapi penutup pelindungnya telah dilepas. Jadi antarmuka internal terekspos.
Semua pengembang beradab – mereka menggunakan mesin kopi sebagaimana mestinya. Tapi salah satu dari mereka, John, memutuskan bahwa dialah yang paling pintar, dan membuat beberapa perubahan pada internal mesin kopi. Jadi mesin kopinya rusak dua hari kemudian.
Itu tentu saja bukan kesalahan John, melainkan kesalahan orang yang melepas penutup pelindung dan membiarkan John melakukan manipulasinya.
Hal yang sama dalam pemrograman. Jika pengguna suatu kelas akan mengubah hal-hal yang tidak dimaksudkan untuk diubah dari luar – konsekuensinya tidak dapat diprediksi.
Dapat didukung
Situasi dalam pemrograman lebih kompleks dibandingkan dengan mesin kopi di kehidupan nyata, karena kita tidak hanya membelinya sekali saja. Kode ini terus mengalami pengembangan dan peningkatan.
Jika kita membatasi antarmuka internal secara ketat, maka pengembang kelas dapat dengan bebas mengubah properti dan metode internalnya, bahkan tanpa memberi tahu pengguna.
Jika Anda seorang pengembang kelas tersebut, senang mengetahui bahwa metode privat dapat diganti namanya dengan aman, parameternya dapat diubah, dan bahkan dihapus, karena tidak ada kode eksternal yang bergantung padanya.
Bagi pengguna, ketika versi baru keluar, mungkin diperlukan perombakan total secara internal, namun tetap mudah untuk ditingkatkan jika antarmuka eksternalnya sama.
Menyembunyikan kompleksitas
Orang suka menggunakan hal-hal yang sederhana. Setidaknya dari luar. Apa yang ada di dalamnya adalah hal yang berbeda.
Pemrogram tidak terkecuali.
Akan lebih mudah jika detail implementasi disembunyikan, dan tersedia antarmuka eksternal yang sederhana dan terdokumentasi dengan baik.
Untuk menyembunyikan antarmuka internal kami menggunakan properti yang dilindungi atau pribadi:
Bidang yang dilindungi dimulai dengan _
. Itu adalah konvensi yang terkenal, tidak diterapkan pada tingkat bahasa. Pemrogram hanya boleh mengakses bidang yang dimulai dengan _
dari kelasnya dan kelas yang mewarisinya.
Bidang pribadi dimulai dengan #
. JavaScript memastikan kita hanya dapat mengaksesnya dari dalam kelas.
Saat ini, kolom pribadi tidak didukung dengan baik di antara browser, namun dapat diisi ganda.