Bagaimana pewarisan prototypal secara praktis berbeda dari warisan klasik?

27

Warisan, Polimorfisme, dan Enkapsulasi adalah tiga fitur paling penting dan paling penting dari OOP, dan dari mereka, pewarisan memiliki statistik penggunaan yang tinggi dewasa ini. Saya sedang belajar JavaScript, dan di sini, mereka semua mengatakan bahwa ia memiliki warisan prototipal, dan orang-orang di mana-mana mengatakan bahwa itu adalah sesuatu yang jauh berbeda dari warisan klasik.

Namun, saya tidak dapat memahami apa perbedaan mereka dari sudut penggunaan praktis? Dengan kata lain, ketika Anda mendefinisikan kelas dasar (prototipe) dan kemudian mengambil beberapa subclass dari itu, Anda berdua memiliki akses ke fungsionalitas kelas dasar Anda, dan Anda dapat menambah fungsi pada kelas turunan. Jika kita menganggap apa yang saya katakan sebagai hasil yang dimaksudkan dari warisan, lalu mengapa kita harus peduli jika kita menggunakan versi prototypal atau klasik?

Untuk lebih jelas, saya tidak melihat perbedaan dalam kegunaan dan pola penggunaan prototipe dan pewarisan klasik. Ini membuat saya tidak tertarik untuk mempelajari mengapa mereka berbeda, karena keduanya menghasilkan hal yang sama, OOAD. Bagaimana praktis (bukan secara teoritis) pewarisan prototypal berbeda dari warisan klasik?

Saeed Neamati
sumber

Jawaban:

6

Posting blog terbaru tentang JS OO

Saya percaya apa yang Anda membandingkan adalah emulasi OO klasik dalam JavaScript dan OO klasik dan tentu saja Anda tidak dapat melihat perbedaan.

Penafian: ganti semua referensi ke "prototypal OO" dengan "prototypal OO dalam JavaScript". Saya tidak tahu secara spesifik tentang Self atau implementasi lainnya.

Namun prototipe OO berbeda. Dengan prototipe Anda hanya memiliki objek dan Anda hanya bisa menyuntikkan objek ke rantai prototipe objek lain. Setiap kali Anda mengakses properti pada objek Anda mencari objek itu dan objek apa pun dalam rantai prototipe.

Untuk prototipe OO tidak ada gagasan enkapsulasi. Enkapsulasi adalah fitur lingkup, penutupan dan fungsi kelas satu tetapi tidak ada hubungannya dengan OO prototipe. Juga tidak ada gagasan tentang pewarisan, yang oleh orang disebut "pewarisan" sebenarnya hanyalah polimorfisme.

Namun memiliki polimorfisme.

Sebagai contoh

var Dog = {
  walk: function() { console.log("walks"); }
}

var d = Object.create(Dog);
d.walk();

Jelas dmemiliki akses ke metode Dog.walkdan ini menunjukkan polimorfisme.

Jadi sebenarnya ada perbedaan besar . Anda hanya memiliki polimorfisme.

Namun seperti yang disebutkan, jika Anda ingin melakukannya (saya tidak tahu mengapa Anda mau), Anda dapat meniru OO klasik dalam JavaScript dan memiliki akses ke enkapsulasi dan pewarisan (terbatas).

Raynos
sumber
1
@ Rayon, Google untuk warisan prototipe dan Anda akan melihat bahwa warisan prototipe muncul. Bahkan dari Yahoo!
Saeed Neamati
@Sisa warisan adalah istilah yang tidak jelas dan biasanya disalahgunakan. Apa yang mereka maksud dengan "pewarisan" saya beri label "polimorfisme". Definisi itu terlalu kabur.
Raynos
Tidak @Rayons, maksud saya kata prototypal adalah istilah yang benar, bukan prototipe . Saya tidak berbicara tentang warisan :)
Saeed Neamati
1
@Memastikan itu salah satu kesalahan ketik itu yang pikiran saya kosong. Saya tidak menepuk perhatian itu
Raynos
1
Hmm .. terminologi. ick. Saya akan menyebut cara objek javascript terkait dengan prototipe mereka "delegasi". Bagi saya, polimorfisme adalah masalah tingkat yang lebih rendah, berdasarkan fakta bahwa nama yang diberikan mungkin menunjuk pada kode yang berbeda untuk objek yang berbeda.
Sean McMillan
15

Warisan klasik mewarisi perilaku, tanpa status apa pun, dari kelas induk. Itu mewarisi perilaku pada saat objek dipakai.

Warisan prototipe mewarisi perilaku dan negara dari objek induk. Ini mewarisi perilaku dan keadaan saat objek dipanggil. Ketika objek induk berubah pada saat run-time, keadaan dan perilaku objek anak terpengaruh.

"Keuntungan" dari pewarisan prototypal adalah bahwa Anda dapat "menambal" keadaan dan perilaku setelah semua objek Anda dipakai. Sebagai contoh, dalam kerangka Ext JS itu umum untuk memuat "menimpa" yang menambal komponen inti kerangka kerja setelah kerangka kerja telah dipakai.

Joeri Sebrechts
sumber
4
Dalam praktiknya, jika Anda mewarisi keadaan, Anda menyiapkan diri untuk dunia yang terluka. State yang diwariskan akan dibagikan jika ia hidup pada objek lain.
Sean McMillan
1
Itu terjadi kepada saya bahwa dalam javascript benar-benar tidak ada konsep perilaku yang terpisah dari negara. Selain fungsi konstruktor, semua perilaku dapat ditetapkan / dimodifikasi pasca-objek-penciptaan sama seperti negara lainnya.
Joeri Sebrechts
Jadi Python tidak memiliki warisan klasik? Jika saya memiliki class C(object): def m(self, x): return x*2dan instance = C()kemudian ketika saya menjalankan instance.m(3)saya dapatkan 6. Tetapi jika saya kemudian mengubahnya Cjadi C.m = lambda s, x: x*xdan saya jalankan instance.m(3)sekarang saya dapatkan 9. Hal yang sama berlaku jika saya membuat class D(C)dan mengubah metode, Cmaka setiap contoh Dmenerima metode yang diubah juga. Apakah saya salah paham atau apakah ini berarti bahwa Python tidak memiliki warisan Klasik menurut definisi Anda?
mVChr
@ mVChr: ​​Anda masih mewarisi perilaku dan tidak menyatakan dalam kasus itu, yang menunjuk ke warisan klasik, tapi itu menunjukkan masalah dengan definisi saya tentang "mewarisi perilaku saat objek itu dipakai". Saya tidak yakin bagaimana cara memperbaiki definisi.
Joeri Sebrechts
9

Pertama: Sebagian besar waktu, Anda akan menggunakan objek, tidak mendefinisikannya, dan menggunakan objek adalah sama di bawah kedua paradigma.

Kedua: Sebagian besar lingkungan prototypal menggunakan divisi yang sama dengan lingkungan berbasis kelas - data yang dapat diubah pada instance, dengan metode yang diwarisi. Jadi ada sedikit perbedaan lagi. (Lihat jawaban saya untuk pertanyaan stack overflow ini , dan Program Pengorganisasian Kertas Mandiri Tanpa Kelas . Lihatlah Citeseer untuk versi PDF .)

Ketiga: Sifat dinamis Javascript memiliki pengaruh yang jauh lebih besar daripada jenis warisan. Fakta bahwa saya dapat menambahkan metode baru ke semua instance dari suatu jenis dengan menetapkannya ke objek dasar rapi, tapi saya bisa melakukan hal yang sama di Ruby, dengan membuka kembali kelas.

Keempat: Perbedaan praktisnya kecil, sementara masalah praktis lupa menggunakan newjauh lebih besar - yaitu, Anda jauh lebih mungkin terpengaruh oleh kehilangan nilai newdaripada Anda akan terpengaruh oleh perbedaan antara kode prototipe dan kode klasik .

Semua yang mengatakan, perbedaan praktis antara pewarisan prototypal dan klasik adalah bahwa hal-hal Anda-yang-tahan-metode (kelas) adalah sama dengan hal-hal-yang-tahan-data Anda (contoh.) Ini berarti bahwa Anda dapat membangun kelas Anda sedikit demi sedikit, menggunakan semua alat manipulasi objek yang sama yang akan Anda gunakan pada contoh apa pun. (Ini, pada kenyataannya, bagaimana semua perpustakaan emulasi kelas melakukannya. Untuk pendekatan yang tidak terlalu seperti semua yang lain, lihat Traits.js ). Ini terutama menarik jika Anda metaprogramming.

Sean McMillan
sumber
ya, jawaban terbaik IMO!
Aivar
0

Warisan prototipe dalam JavaScript berbeda dari kelas dalam cara-cara penting ini:

Konstruktor hanyalah fungsi yang dapat Anda panggil tanpa new:

function Circle (r, x, y) { 
  //stuff here
}
Var c = new Circle();
Circle.call(c, x, y, z); //This works and you can do it over and over again.

Tidak ada variabel atau metode pribadi, yang terbaik Anda dapat melakukan ini:

function Circle (r, x, y) {
  var color = 'red';
  function drawCircle () {
    //some code here with x, y and r
  }
  drawCircle();
  this.setX = function (x_) {
    x = x_;
    drawCircle();
  }

}
Circle.prototype.getX = function () {
   //Can't access x!
}

Dalam contoh sebelumnya, Anda tidak dapat memperluas kelas secara bermakna jika Anda menggunakan variabel dan metode pribadi palsu dan sebagai tambahan metode publik apa pun yang telah Anda nyatakan akan dibuat ulang setiap kali instance baru dibuat.

Bjorn
sumber
Anda dapat memperluas "kelas" secara bermakna. Anda tidak dapat mengakses variabel lokal color, r, x, ydan drawCircleyang terikat pada lingkup leksikalCircle
Raynos
Memang benar, Anda bisa, tetapi Anda tidak bisa melakukannya tanpa membuat banyak metode 'publik' yang ditambahkan ke konteks yang dibuat setiap kali Anda membuat sebuah instance.
Bjorn
Ini adalah pola yang sangat aneh yang Anda gunakan di sana. Circle.call()sebagai konstruktor? Sepertinya Anda mencoba menggambarkan "objek fungsional" (keliru ...)
Sean McMillan
Ini bukan sesuatu yang pernah saya lakukan, saya hanya mengatakan mungkin untuk memanggil konstruktor berulang kali dengan JavaScript tetapi tidak dalam bahasa dengan kelas tradisional.
Bjorn
1
Tetapi bagaimana perbedaan ini 'penting'? Saya tidak melihat membangun objek dengan memanggil fungsi, tidak menggunakan 'baru', sebagai 'penting', hanya perbedaan sintaksis kecil. Demikian juga, tidak memiliki variabel pribadi tidak berbeda secara mendasar, hanya perbedaan kecil. Plus, Anda dapat memiliki (sesuatu yang mirip dengan) variabel pribadi, seperti yang Anda gambarkan.
Roel