Apa kelebihan OOP berbasis prototipe dibandingkan OOP berbasis kelas?


47

Ketika saya pertama kali mulai pemrograman Javascript setelah terutama berurusan dengan OOP dalam konteks bahasa berbasis kelas, saya bingung mengapa OOP berbasis prototipe lebih disukai daripada OOP berbasis kelas.

  1. Apa keuntungan struktural untuk menggunakan OOP berbasis prototipe, jika ada? (mis. Apakah kita mengharapkannya menjadi lebih cepat atau kurang intensif memori dalam aplikasi tertentu?)
  2. Apa keuntungan dari perspektif pembuat kode? (mis. Apakah lebih mudah untuk membuat kode aplikasi tertentu atau memperluas kode orang lain menggunakan prototyping?)

Tolong jangan melihat pertanyaan ini sebagai pertanyaan tentang Javascript pada khususnya (yang telah memiliki banyak kesalahan selama bertahun-tahun yang sama sekali tidak terkait dengan pembuatan prototipe). Sebagai gantinya, silakan lihat dalam konteks keuntungan teoritis dari prototyping vs kelas.

Terima kasih.


Jawaban:


46

Saya punya cukup banyak pengalaman dari kedua pendekatan saat menulis game RPG di Jawa. Awalnya saya menulis seluruh permainan menggunakan OOP berbasis kelas, tetapi akhirnya menyadari bahwa ini adalah pendekatan yang salah (itu menjadi tidak dapat dipertahankan saat hierarki kelas diperluas). Karena itu saya mengonversi seluruh basis kode menjadi kode berbasis prototipe. Hasilnya jauh lebih baik dan lebih mudah dikelola.

Kode sumber di sini jika Anda tertarik ( Tyrant - Java Roguelike )

Inilah manfaat utama:

  • Ini sepele untuk membuat "kelas" baru - cukup salin prototipe dan ubah beberapa properti dan voila ... kelas baru. Saya menggunakan ini untuk mendefinisikan jenis ramuan baru misalnya dalam 3-6 baris masing-masing Java. Jauh lebih baik daripada file kelas baru dan banyak boilerplate!
  • Dimungkinkan untuk membangun dan mempertahankan "kelas" yang sangat besar dengan kode yang relatif sedikit - Tyrant misalnya memiliki sekitar 3000 prototipe berbeda dengan hanya sekitar 42.000 baris total kode. Itu cukup luar biasa untuk Jawa!
  • Banyak pewarisan mudah - Anda cukup menyalin subset properti dari satu prototipe dan menempelkannya di atas properti di prototipe lain. Dalam RPG misalnya, Anda mungkin menginginkan "golem baja" memiliki beberapa properti "objek baja" dan beberapa properti "golem" dan beberapa properti "monster tidak cerdas". Mudah dengan prototipe, coba lakukan itu dengan pusaka warisan ......
  • Anda dapat melakukan hal-hal cerdas dengan pengubah properti - dengan menempatkan logika pintar dalam metode "baca properti" umum, Anda dapat menerapkan berbagai pengubah. Sebagai contoh, mudah untuk mendefinisikan cincin ajaib yang menambahkan kekuatan +2 untuk siapa pun yang memakainya. Logika untuk ini adalah pada objek cincin, bukan dalam metode "baca kekuatan", jadi Anda menghindari keharusan meletakkan banyak tes bersyarat di tempat lain dalam basis kode Anda (mis. "Apakah karakter yang memakai cincin peningkatan kekuatan?")
  • Instance dapat menjadi templat untuk instance lain - misalnya jika Anda ingin "mengkloning" suatu objek itu mudah, cukup gunakan objek yang ada sebagai prototipe untuk objek baru. Tidak perlu menulis banyak logika kloning yang kompleks untuk kelas yang berbeda.
  • Sangat mudah untuk mengubah perilaku saat runtime - yaitu Anda dapat mengubah properti dan "mengubah" objek secara sewenang-wenang saat runtime. Memungkinkan untuk efek dalam gim yang keren, dan jika Anda menyandingkan ini dengan "bahasa scripting" maka hampir semua hal mungkin terjadi saat runtime.
  • Ini lebih cocok untuk gaya pemrograman "fungsional" - Anda cenderung menemukan diri Anda menulis banyak fungsi yang menganalisis objek tindakan dengan tepat, daripada logika tertanam dalam metode yang melekat pada kelas tertentu. Saya pribadi lebih suka gaya FP ini.

Berikut adalah kelemahan utamanya:

  • Anda kehilangan jaminan pengetikan statis - karena Anda secara efektif membuat sistem objek dinamis. Ini cenderung berarti bahwa Anda perlu menulis lebih banyak tes untuk memastikan perilaku itu benar dan bahwa objek adalah "jenis" yang tepat
  • Ada beberapa overhead kinerja - karena pembacaan properti objek biasanya dipaksa untuk melalui satu atau lebih pencarian peta, Anda membayar sedikit biaya dalam hal kinerja. Dalam kasus saya itu bukan masalah, tetapi bisa menjadi masalah dalam beberapa kasus (misalnya FPS 3D dengan banyak objek yang ditanyai di setiap frame)
  • Refactoring tidak bekerja dengan cara yang sama - dalam sistem berbasis prototipe Anda pada dasarnya "membangun" warisan pusaka Anda dengan kode. Alat IDE / refactoring tidak dapat membantu Anda karena mereka tidak dapat memperbaiki pendekatan Anda. Saya tidak pernah menemukan ini masalah, tetapi bisa lepas kendali jika Anda tidak hati-hati. Anda mungkin ingin pengujian untuk memeriksa apakah hierarki warisan Anda dibuat dengan benar!
  • Itu agak asing - orang yang terbiasa dengan gaya OOP konvensional dapat dengan mudah menjadi bingung. "Apa maksudmu hanya ada satu kelas yang disebut" Hal "?!?" - "Bagaimana cara memperpanjang kelas Hal terakhir ini!?!" - "Kamu melanggar prinsip OOP !!!" - "Adalah salah untuk memiliki semua fungsi statis ini yang bekerja pada objek apa pun!?!?"

Akhirnya beberapa catatan implementasi:

  • Saya menggunakan Java HashMap untuk properti dan pointer "parent" untuk prototipe. Ini berfungsi dengan baik tetapi memiliki kelemahan berikut: a) properti yang dibaca kadang-kadang harus ditelusuri kembali melalui rantai induk yang lama, melukai kinerja b) jika Anda bermutasi prototipe orangtua, perubahan akan mempengaruhi semua anak yang tidak menimpa perubahan properti. Ini dapat menyebabkan bug halus jika Anda tidak berhati-hati!
  • Jika saya melakukan ini lagi, saya akan menggunakan peta persisten abadi untuk properti (seperti peta persisten Clojure ), atau implementasi peta hash persisten Java saya sendiri . Maka Anda akan mendapatkan manfaat dari penyalinan / perubahan murah ditambah dengan perilaku tidak berubah dan Anda tidak perlu menautkan objek secara permanen ke orang tua mereka.
  • Anda bisa bersenang-senang jika menanamkan fungsi / metode ke properti objek. Retas yang saya gunakan di Jawa untuk ini (subtipe anonim dari kelas "Script") tidak terlalu elegan - jika melakukan ini lagi saya mungkin akan menggunakan bahasa yang mudah disematkan untuk skrip (Clojure atau Groovy)


(+1) Ini analisis yang bagus. Altought, ini lebih didasarkan pada model Java, misalnya, Delphi, C #, VB.Net memiliki sifat eksplisit.
umlcat

3
@umlcat - Saya merasa model Java hampir sama dengan model Delphi / C # (terlepas dari gula sintaksis yang bagus untuk akses properti) - Anda masih harus secara statis mendeklarasikan properti yang Anda inginkan dalam definisi kelas Anda. Inti dari model prototipe adalah bahwa definisi ini tidak statis dan Anda tidak perlu membuat pernyataan terlebih dahulu ....
mikera

Ini melewatkan yang besar. Anda dapat mengubah prototipe, yang secara efektif mengubah properti pada setiap instance, bahkan setelah mereka dibuat tanpa menyentuh konstruktor prototipe.
Erik Reppen

Mengenai multiple inheritance lebih mudah, Anda membandingkannya dengan Java yang tidak mendukung multiple inheritance, tetapi apakah lebih mudah dibandingkan dengan bahasa yang mendukungnya seperti C ++?
Piovezan

2

Keuntungan utama OOP berbasis prototipe adalah bahwa objek & "kelas" dapat diperpanjang saat runtime.

Di OOP berbasis-kelas, ada beberapa fitur bagus, sayangnya, itu tergantung pada bahasa pemrograman.

Object Pascal (Delphi), VB.Net & C # memiliki cara yang sangat langsung untuk menggunakan properti (jangan dikacaukan dengan bidang) dan metode akses properti, sedangkan properti Java & C ++, diakses oleh metode. Dan PHP memiliki campuran keduanya, yang disebut "metode ajaib".

Ada beberapa kelas pengetikan dinamis, meskipun bahasa OO kelas utama memiliki pengetikan statis. Saya pikir mengetik statis dengan Kelas OO sangat berguna, karena memungkinkan fitur yang disebut Object Introspection yang memungkinkan untuk membuat IDE dan mengembangkan halaman web, secara visual, dan cepat.


0

Saya harus setuju dengan @umlcat. Ekstensi kelas adalah keuntungan utama. Misalnya, Anda ingin menambahkan lebih banyak fungsi ke kelas string dalam jangka waktu yang lama. Dalam C ++ Anda akan melakukan ini melalui pewarisan lanjutan dari generasi kelas string sebelumnya. Masalah dengan pendekatan ini adalah bahwa setiap generasi pada dasarnya menjadi jenis yang berbeda dari itu sendiri yang dapat menyebabkan penulisan ulang basis kode yang ada. Dengan pewarisan prototipikal Anda cukup 'melampirkan' metode baru ke kelas dasar asli ..., tidak ada yang besar membangun kelas warisan dan hubungan warisan di mana-mana. Saya akan senang melihat C ++ datang dengan mekanisme ekstensi serupa di standar baru mereka. Tetapi komite mereka tampaknya dijalankan oleh orang-orang yang ingin menambahkan fitur yang menarik dan populer.


1
Anda mungkin ingin membaca Monoliths "Unstrung" , std::stringsudah memiliki terlalu banyak anggota yang harus berupa algoritma independen atau setidaknya bukan teman bukan anggota. Lagi pula, fungsi anggota baru dapat ditambahkan tanpa mengubah tata letak di memori apa pun jika Anda dapat memodifikasi kelas asli.
Deduplicator
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.