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?
sumber
obj
tidak memilikidoSomething
metode? Apakah pengecualian dikemukakan? Apakah tidak ada yang terjadi?Jawaban:
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.
sumber
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:
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.
sumber
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.
sumber
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.
sumber
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.
sumber
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.
sumber
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.
sumber
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.
sumber
Kebenaran terletak di suatu tempat di tengah. Saya suka bagaimana C # 4.0 yang diketik secara statis mendukung "bebek mengetik" dengan kata kunci "dinamis".
sumber
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.
Di sini kelas
GoldenRetriever
memiliki yang samaSound
denganDog
ucapan terima kasih gratis untuk warisan.Saya akan menulis contoh yang sama dengan tingkat Haskell saya agar Anda dapat melihat perbedaannya
Di sini Anda tidak melarikan diri harus menentukan
sound
untukGoldenRetriever
. Hal termudah secara umum adalahtetapi 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.
sumber
Animal
ini adalah anti-tutorial dari OOP.