Mengapa warisan dan polimorfisme begitu banyak digunakan?


18

Semakin saya belajar tentang paradigma pemrograman yang berbeda, seperti pemrograman fungsional, semakin saya mulai mempertanyakan kebijaksanaan konsep OOP seperti pewarisan dan polimorfisme. Saya pertama kali belajar tentang pewarisan dan polimorfisme di sekolah, dan pada saat itu polimorfisme tampak seperti cara yang hebat untuk menulis kode generik yang memungkinkan perluasan yang mudah.

Tetapi dalam menghadapi mengetik bebek (baik dinamis dan statis) dan fitur fungsional seperti fungsi tingkat tinggi, saya mulai melihat pewarisan dan polimorfisme sebagai memaksakan pembatasan yang tidak perlu berdasarkan pada serangkaian hubungan yang rapuh antara objek. Gagasan umum di balik polimorfisme adalah bahwa Anda menulis suatu fungsi sekali, dan kemudian Anda dapat menambahkan fungsionalitas baru ke program Anda tanpa mengubah fungsi aslinya - yang perlu Anda lakukan hanyalah membuat kelas turunan lain yang mengimplementasikan metode yang diperlukan.

Tapi ini jauh lebih sederhana untuk dicapai melalui mengetik bebek, apakah itu dalam bahasa yang dinamis seperti Python, atau bahasa statis seperti C ++.

Sebagai contoh, perhatikan fungsi Python berikut, diikuti oleh C ++ yang setara:

def foo(obj):
   obj.doSomething()

template <class Obj>
void foo(Obj& obj)
{
   obj.doSomething();
}

Setara dengan OOP akan menjadi seperti kode Java berikut:

public void foo(DoSomethingable obj)
{
  obj.doSomething();
}

Perbedaan utama, tentu saja, adalah bahwa versi Java membutuhkan pembuatan antarmuka atau hierarki warisan sebelum dapat berfungsi. Versi Java dengan demikian melibatkan lebih banyak pekerjaan, dan kurang fleksibel. Selain itu, saya menemukan bahwa sebagian besar hierarki warisan dunia nyata agak tidak stabil. Kita semua telah melihat contoh Bentuk dan Hewan yang dibuat-buat, tetapi di dunia nyata, ketika persyaratan bisnis berubah dan fitur baru ditambahkan, sulit untuk menyelesaikan pekerjaan sebelum Anda perlu benar-benar memperluas hubungan "is-a" antara sub-kelas, atau merombak / memperbaiki hierarki Anda untuk memasukkan kelas dasar atau antarmuka lebih lanjut untuk mengakomodasi persyaratan baru. Dengan mengetik bebek, Anda tidak perlu khawatir tentang pemodelan apa pun - Anda hanya perlu khawatir tentang fungsionalitas yang Anda butuhkan.

Namun, pewarisan dan polimorfisme sangat populer sehingga saya ragu akan terlalu berlebihan untuk menyebut mereka strategi dominan untuk perluasan dan penggunaan kembali kode. Jadi mengapa warisan dan polimorfisme begitu sukses? Apakah saya mengabaikan beberapa keuntungan serius yang dimiliki pewarisan / polimorfisme dibandingkan pengetikan bebek?


Saya bukan ahli Python jadi saya harus bertanya: apa yang akan terjadi jika objtidak memiliki doSomethingmetode? Apakah pengecualian dikemukakan? Apakah tidak ada yang terjadi?
FrustratedWithFormsDesigner

6
@Frustrated: Pengecualian yang sangat jelas dan spesifik dimunculkan.
dsimcha

11
Bukankah mengetik bebek hanya polimorfisme sampai sebelas? Dugaan saya adalah bahwa Anda tidak frustrasi dengan OOP tetapi dengan inkarnasi yang diketik secara statis. Saya bisa mengerti itu, sungguh, tapi itu tidak membuat OOP secara keseluruhan ide yang buruk.

3
Pertama baca "secara luas" sebagai "liar" ...

Jawaban:


22

Saya sebagian besar setuju dengan Anda, tetapi untuk bersenang-senang saya akan bermain Devil's Advocate. Antarmuka eksplisit memberikan satu tempat untuk mencari kontrak yang secara eksplisit ditentukan secara formal , memberi tahu Anda apa yang seharusnya dilakukan oleh suatu jenis. Ini bisa menjadi penting ketika Anda bukan satu-satunya pengembang di suatu proyek.

Selain itu, antarmuka eksplisit ini dapat diimplementasikan lebih efisien daripada mengetik bebek. Panggilan fungsi virtual memiliki overhead yang hampir tidak lebih dari panggilan fungsi normal, kecuali bahwa itu tidak dapat digarisbawahi. Mengetik bebek memiliki overhead yang besar. Pengetikan struktural gaya C ++ - style (using templates) dapat menghasilkan mengasapi file objek dalam jumlah besar (karena setiap instantiation independen pada level file objek) dan tidak berfungsi ketika Anda memerlukan polimorfisme saat runtime, bukan waktu kompilasi.

Intinya: Saya setuju bahwa pewarisan gaya Jawa dan polimorfisme bisa menjadi PITA dan alternatif harus lebih sering digunakan, tetapi masih memiliki kelebihan.


29

Warisan dan polimorfisme banyak digunakan karena mereka bekerja , untuk beberapa jenis masalah pemrograman.

Bukan karena mereka banyak diajarkan di sekolah, itu mundur: mereka banyak diajarkan di sekolah karena orang (alias pasar) menemukan bahwa mereka bekerja lebih baik daripada alat-alat lama, dan sekolah mulai mengajar mereka. [Anekdot: ketika saya pertama kali belajar OOP, sangat sulit untuk menemukan perguruan tinggi yang mengajarkan bahasa OOP apa pun. Sepuluh tahun kemudian, sulit untuk menemukan perguruan tinggi yang tidak mengajarkan bahasa OOP.]

Kamu berkata:

Gagasan umum di balik polimorfisme adalah bahwa Anda menulis suatu fungsi sekali, dan kemudian Anda dapat menambahkan fungsionalitas baru ke program Anda tanpa mengubah fungsi aslinya - yang perlu Anda lakukan adalah membuat kelas turunan lain yang mengimplementasikan metode yang diperlukan

Saya katakan:

Tidak

Apa yang Anda gambarkan bukanlah polimorfisme, tetapi warisan. Tidak heran Anda mengalami masalah OOP! ;-)

Cadangkan langkah: polimorfisme adalah manfaat dari penyampaian pesan; itu hanya berarti bahwa setiap objek bebas untuk menanggapi pesan dengan caranya sendiri.

Jadi ... Mengetik Bebek adalah (atau lebih tepatnya, memungkinkan) polimorfisme

Inti dari pertanyaan Anda tampaknya bukan bahwa Anda tidak mengerti OOP atau bahwa Anda tidak menyukainya, tetapi bahwa Anda tidak suka mendefinisikan antarmuka . Tidak apa-apa, dan selama Anda berhati-hati, semua akan berjalan baik. Kekurangannya adalah bahwa jika Anda membuat kesalahan - menghapus metode, misalnya - Anda tidak akan mengetahuinya sampai waktu berjalan.

Itu hal yang statis vs dinamis, yang merupakan diskusi setua Lisp, dan tidak terbatas pada OOP.


2
+1 untuk "mengetik bebek adalah polimorfisme". Polimorfisme dan pewarisan secara tidak sadar dirantai bersama terlalu lama, dan pengetikan statis yang ketat adalah satu-satunya alasan sebenarnya mereka harus melakukannya.
cHao

+1. Saya pikir perbedaan statis vs dinamis harus lebih dari catatan - itu adalah inti dari perbedaan antara mengetik bebek dan polimorfisme gaya java.
Sava B.

8

Saya sudah mulai melihat warisan dan polimorfisme sebagai memaksakan pembatasan yang tidak perlu berdasarkan pada serangkaian hubungan yang rapuh antara objek.

Mengapa?

Warisan (dengan atau tanpa mengetik bebek) memastikan penggunaan kembali fitur umum. Jika itu umum, Anda dapat memastikan bahwa itu digunakan kembali secara konsisten dalam subkelas.

Itu saja. Tidak ada "batasan yang tidak perlu". Ini penyederhanaan.

Polimorfisme, sama halnya, adalah apa yang dimaksud dengan "mengetik bebek". Metode yang sama. Banyak kelas dengan antarmuka yang identik, namun implementasinya berbeda.

Itu bukan batasan. Ini penyederhanaan.


7

Warisan dilecehkan, tetapi begitu juga mengetik bebek. Keduanya dapat dan memang mengarah pada masalah.

Dengan pengetikan yang kuat Anda mendapatkan banyak "unit test" yang dilakukan pada waktu kompilasi. Dengan mengetik bebek, Anda sering harus menulisnya.


3
Komentar Anda tentang pengujian hanya berlaku untuk pengetikan bebek dinamis. C ++ - style duck typing statis (dengan templat) memberi Anda kesalahan pada waktu kompilasi. (Meskipun, kesalahan template C ++ yang diberikan cukup sulit untuk diuraikan, tapi itu masalah yang sama sekali berbeda.)
Channel72

2

Hal yang baik tentang mempelajari hal-hal di sekolah adalah bahwa Anda lakukan belajar mereka. Hal yang tidak begitu baik adalah Anda mungkin menerimanya sedikit terlalu dogmatis, tanpa memahami kapan mereka berguna dan kapan tidak.

Kemudian, jika Anda telah mempelajarinya secara dogmatis, Anda kemudian dapat "memberontak" seperti dogmatis ke arah lain. Itu juga tidak baik.

Seperti halnya ide-ide semacam itu, yang terbaik adalah mengambil pendekatan pragmatis. Kembangkan pemahaman tentang di mana mereka cocok dan di mana mereka tidak cocok. Dan abaikan semua cara mereka telah oversold.


2

Ya, pengetikan dan antarmuka statis adalah batasan. Tetapi segala sesuatu sejak pemrograman terstruktur ditemukan (yaitu "dianggap berbahaya") adalah tentang membatasi kami. Paman Bob sangat senang dalam hal ini di blog videonya .

Sekarang, orang dapat berpendapat bahwa penyempitan itu buruk, tetapi di sisi lain penyempitan membawa keteraturan, kontrol, dan keakraban dengan topik yang sangat rumit.

Kendala yang merilekskan dengan (kembali) memperkenalkan pengetikan dinamis dan bahkan akses memori langsung adalah konsep yang sangat kuat, tetapi juga bisa membuat lebih sulit bagi banyak programmer untuk berurusan dengan. Terutama programmer yang terbiasa mengandalkan kompiler dan mengetikkan keselamatan untuk sebagian besar pekerjaan mereka.


1

Warisan adalah hubungan yang sangat kuat antara dua kelas. Saya tidak berpikir Jawa lebih kuat. Karena itu, Anda hanya boleh menggunakannya saat Anda bersungguh-sungguh. Warisan publik adalah hubungan "is-a", bukan "biasanya is-a". Sangat, sangat mudah untuk menggunakan warisan secara berlebihan dan berakhir berantakan. Dalam banyak kasus, pewarisan digunakan untuk mewakili "has-a" atau "take-functional-from-a", dan itu biasanya lebih baik dilakukan dengan komposisi.

Polimorfisme adalah konsekuensi langsung dari hubungan "is-a". Jika Berasal mewarisi dari Basis, maka setiap Basis yang Diperoleh "adalah-a", dan karena itu Anda dapat menggunakan Berasal di mana pun Anda akan menggunakan Basis. Jika ini tidak masuk akal dalam hierarki warisan, maka hierarki itu salah dan mungkin ada terlalu banyak warisan yang terjadi.

Mengetik bebek adalah fitur yang rapi, tetapi kompiler tidak akan memperingatkan Anda jika Anda akan menyalahgunakannya. Jika Anda tidak ingin harus menangani pengecualian pada saat run-time, Anda harus meyakinkan diri sendiri bahwa Anda mendapatkan hasil yang tepat setiap saat. Mungkin lebih mudah hanya dengan mendefinisikan hierarki pewarisan statis.

Saya bukan penggemar nyata pengetikan statis (saya menganggapnya sering menjadi bentuk optimasi prematur), tetapi itu menghilangkan beberapa kelas kesalahan, dan banyak orang berpikir kelas-kelas itu layak dihilangkan.

Jika Anda menyukai pengetikan dinamis dan pengetikan bebek lebih baik daripada pengetikan statis dan hierarki pewarisan yang ditentukan, tidak masalah. Namun, cara Java memang memiliki kelebihan.


0

Saya perhatikan bahwa semakin saya menggunakan penutupan C #, semakin sedikit saya melakukan OOP tradisional. Warisan adalah satu-satunya cara untuk dengan mudah berbagi implementasi, jadi saya pikir itu sering digunakan secara berlebihan dan batasan konsepsi didorong terlalu jauh.

Meskipun Anda biasanya dapat menggunakan penutupan untuk melakukan sebagian besar dari apa yang akan Anda lakukan dengan warisan, itu juga bisa menjadi jelek.

Pada dasarnya ini adalah alat yang tepat untuk situasi pekerjaan: OOP tradisional dapat bekerja dengan sangat baik ketika Anda memiliki model yang sesuai dengan itu, dan penutupan dapat bekerja dengan sangat baik ketika Anda tidak.


-1

Kebenaran terletak di suatu tempat di tengah. Saya suka bagaimana C # 4.0 yang diketik secara statis mendukung "bebek mengetik" dengan kata kunci "dinamis".


-1

Warisan, bahkan ketika alasan dari perspektif KB, adalah konsep yang hebat; itu tidak hanya menghemat banyak waktu, itu memberi makna pada hubungan antara objek-objek tertentu dalam program Anda.

public class Animal {
    public virtual string Sound () {
        return "Some Sound";
    }
}

public class Dog : Animal {
    public override string Sound () {
        return "Woof";
    }
}

public class Cat : Animal {
    public override string Sound () {
        return "Mew";
    }
}

public class GoldenRetriever : Dog {

}

Di sini kelas GoldenRetrievermemiliki yang sama Sounddengan Dogucapan terima kasih gratis untuk warisan.

Saya akan menulis contoh yang sama dengan tingkat Haskell saya agar Anda dapat melihat perbedaannya

data Animal = Animal | Dog | Cat | GoldenRetriever

sound :: Animal -> String
sound Animal = "Some Sound"
sound Dog = "Woof"
sound Cat = "Mew"
sound GoldenRetriever = "Woof"

Di sini Anda tidak melarikan diri harus menentukan sounduntuk GoldenRetriever. Hal termudah secara umum adalah

sound GoldenRetriever = sound Dog

tetapi hanya imagen jika Anda memiliki 20 fungsi! Jika ada pakar Haskell, tolong tunjukkan kami cara yang lebih mudah.

Yang mengatakan, akan lebih baik untuk memiliki pencocokan pola dan pewarisan pada saat yang sama, di mana suatu fungsi akan default ke kelas dasar jika instance saat ini tidak memiliki implementasi.


5
Saya bersumpah, Animalini adalah anti-tutorial dari OOP.
Telastyn

@Telastyn Saya belajar contoh dari Derek Banas di Youtube. Guru yang baik. Menurut pendapat saya itu sangat mudah untuk divisualisasikan dan dipikirkan. Anda bebas mengedit posting dengan contoh yang lebih baik.
Cristian Garcia

ini sepertinya tidak menambah sesuatu yang substansial daripada 10 jawaban sebelumnya
agas

@gnat Ketika "substansi" sudah ada di luar sana, seseorang yang baru mengenal subjek mungkin menemukan contoh yang lebih mudah dipahami.
Cristian Garcia

2
Hewan dan bentuk adalah aplikasi polimorfisme yang sangat buruk karena beberapa alasan yang telah dibahas secara ad mual di situs ini. Pada dasarnya, mengatakan kucing "adalah" hewan tidak ada artinya, karena tidak ada "binatang" konkret. Apa yang sebenarnya Anda maksudkan dengan model adalah perilaku, bukan identitas. Mendefinisikan antarmuka "CanSpeak" yang dapat diimplementasikan sebagai meow atau kulit kayu masuk akal. Mendefinisikan antarmuka "HasArea" yang dapat diimplementasikan oleh persegi dan lingkaran masuk akal, tetapi bukan Bentuk generik.
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.