Mengapa tidak semua metode virtual atau mengapa tidak memiliki setiap kelas setidaknya satu antarmuka?

8

Ini adalah pertanyaan yang lebih filosofis, yang membahas platform .NET, tapi mungkin itu berguna juga untuk bahasa lain. Saya melakukan banyak Pengujian Unit dan terutama ketika saya menggunakan komponen pihak ketiga yang sering saya perjuangkan. Dalam. NET adalah klaim besar untuk desain komponen (er) untuk memilih metode mana yang harus virtual atau tidak. Di satu sisi adalah penggunaan komponen (yang masuk akal untuk menjadi virtual), di sisi lain apakah itu komponen yang dapat diperolok-olok . Anda dapat menggunakan Shims untuk mempermainkan komponen pihak ketiga, tetapi ini sering menyebabkan desain dan kompleksitas yang buruk. Seperti yang saya ingat diskusi tentang membuat semua metode menjadi virtual (JAVA memilikinya sejak awal) di .NET adalah tentang kinerja. Tapi apakah masih masalah? Mengapa tidak semua metode dalam. NET virtual atau mengapa tidak memiliki setiap kelas setidaknya satu antarmuka?

Anton Kalcik
sumber
1
Ah ya karena setelah beberapa menit seseorang menurunkan saya (pertama kali) saya ingin tahu mengapa.
Anton Kalcik
Saya menduga orang yang memilih untuk menutup pertanyaan ini sebagai "terlalu luas" juga memilih Anda.
David Arno
1
"Programmer Stack Exchange adalah situs tanya jawab untuk pemrogram profesional yang tertarik dengan pertanyaan konseptual tentang pengembangan perangkat lunak" Apakah saya memahami sesuatu yang salah?
Anton Kalcik
5
Saya cenderung setuju bahwa pertanyaan ini terlalu luas. "Mengapa setiap kelas tidak memiliki virtual?" Kenapa harus begitu? Anda menyebutkan pengujian. Mungkin Anda bisa meningkatkan pertanyaan dengan mengubahnya menjadi "mengapa kelas ini (yang paling menjengkelkan .net class) tidak memiliki metode ini ditandai virtual?" Namun Anda mungkin menemukan bahwa jawabannya adalah "Oh yeah. Mereka tidak benar-benar mengantisipasi ledakan TDD ketika merancang. NET 1.0"
perfeksionis
4
Jika Anda ingin membuatnya luas karena Anda hanya tertarik pada pendapat, maka itu di luar topik karena dua alasan: terlalu luas dan berdasarkan pendapat .
Jörg W Mittag

Jawaban:

7

Seperti yang dikatakan Anders , sebagian tentang kinerja dan sebagian lagi tentang mengunci rancangan yang buruk untuk mengurangi ruang lingkup masalah yang terjadi kemudian oleh orang-orang yang secara buta mewarisi hal-hal yang tidak dirancang untuk diwariskan.

Performa tampak jelas - sementara sebagian besar metode tidak akan diperhatikan pada perangkat keras modern, pengambil virtual mungkin menyebabkan hit yang cukup mencolok, bahkan pada perangkat keras modern yang semakin bergantung pada instruksi yang diprediksi dengan mudah dalam pipa CPU.

Sekarang alasan untuk membuat semuanya virtual secara default tampaknya didorong oleh satu hal saja: kerangka kerja unit testing. Tampaknya bagi saya bahwa ini adalah alasan yang buruk, di mana kami mengubah kode agar sesuai dengan kerangka kerja daripada mendesainnya dengan benar.

Mungkin masalahnya adalah dengan kerangka kerja yang digunakan di sini, mereka harus meningkatkan untuk memungkinkan penggantian fungsi saat runtime dengan shims yang disuntikkan daripada membangun kode yang melakukan ini (dengan semua peretasan untuk mendapatkan putaran metode pribadi maupun yang non-virtual, tidak untuk menyebutkan fungsi statis dan pihak ketiga)

Jadi metode alasan tidak virtual secara default adalah seperti yang dikatakan Anders, dan setiap keinginan untuk membuatnya virtual agar sesuai dengan beberapa alat unit test adalah menempatkan kereta di depan kuda, desain yang tepat mengalahkan kendala buatan.

Sekarang Anda dapat mengurangi semua ini dengan menggunakan antarmuka, atau dengan mengerjakan ulang seluruh basis kode Anda untuk menggunakan komponen (atau layanan Microsoft) yang berkomunikasi melalui pesan yang lewat alih-alih panggilan metode kabel langsung. Jika Anda memperbesar permukaan unit untuk pengujian menjadi komponen, dan komponen itu sepenuhnya mandiri, Anda tidak perlu mengacaukannya untuk mengujinya.

gbjbaanb
sumber
Saya sangat setuju.
Robert Harvey
@ gbjbaanb Saya tidak mengerti kalimat terakhir "Jika Anda memperbesar permukaan unit untuk pengujian menjadi komponen, dan komponen itu sepenuhnya mandiri, Anda tidak benar-benar perlu mengacaukannya untuk unit mengujinya. "
Anton Kalcik
Jawaban yang sangat bagus (lebih baik dari saya, saya rasa :)
David Arno
1
@AntonKalcik Maksudku, jika Anda memiliki kotak hitam maka seharusnya tidak memiliki dependensi yang perlu diejek. Yaitu, Anda mengujinya dengan menembakkan input dan melihat apa yang keluar. Jika kotak Anda kecil dan Anda telah mendesainnya agar dipisahkan dari layanan lain maka ini dapat terjadi dengan mudah. Masalahnya adalah bahwa suatu kelas tidak de-coupable dari kelas lain dengan sangat mudah, dan karenanya membutuhkan ejekan. Jika Anda menganggap unit sebagai komponen (mungkin dll, atau layanan mikro) daripada kelas, Anda dapat menguji dengan cara ini.
gbjbaanb
Mengenai kinerja: CPU dapat dengan mudah memprediksi panggilan metode virtual (jika selalu sama). Masalah besar adalah bahwa jauh lebih sulit bagi kompiler JIT untuk inline panggilan metode virtual, yang kemudian mencegah optimasi lebih lanjut.
svick
6

Ada dua elemen untuk pertanyaan Anda, jadi saya akan mencoba dan mengatasinya pada gilirannya:

  1. Mengapa metode .NET tidak virtual secara default?

    Warisan, dalam praktiknya, memiliki banyak masalah . Jadi jika ingin digunakan sebagai bagian dari desain, maka harus dipertimbangkan dengan cermat. Dengan membuat metode non-virtual secara default, teorinya adalah itu mendorong pengembang untuk berpikir tentang warisan ketika merancang kelas. Apakah itu berhasil dalam praktiknya adalah masalah lain tentu saja.

  2. Mengapa tidak semua kelas menerapkan antarmuka?

    Desain yang buruk adalah jawaban yang sederhana. Meskipun secara umum diakui sebagai praktik yang baik untuk waktu yang sangat lama, sayangnya banyak orang masih tidak merancang sistem mereka dengan mempertimbangkan DI dan TDD.

Kombinasi kelas yang tidak sepenuhnya diwariskan dan tidak mudah dipermainkan memang menciptakan "badai sempurna" kode yang sulit untuk diuji. Sampai kita mencapai hari ketika semua orang menggunakan DI dan prinsip desain-ke-antarmuka, tidak banyak yang bisa dilakukan untuk mengurangi rasa sakit.

David Arno
sumber
3
"Desain yang buruk adalah jawaban yang sederhana" - Saya cenderung setuju, tetapi ada pengecualian. Lihat objek transfer data atau merekam objek. Untuk objek sederhana POCO / POJO / masukkan bahasa di sini, tidak masuk akal bagi mereka untuk menerapkan interfacehanya karena Nilai apa yang dimiliki interfaceuntuk apa yang pada dasarnya dimiliki oleh tuple yang dimuliakan? (Meskipun mungkin menggunakan POJO / POCO sendiri adalah cacat desain)
Dan Pantry
2
@AntonKalcik, tim C # mengadopsi pendekatan yang sangat konservatif "tidak pernah memperkenalkan perubahan yang melanggar" pada bahasa dan membuat antarmuka wajib, pasti akan menjadi perubahan besar. Jadi saya curiga Anda tidak akan melihatnya ditambahkan ke C #.
David Arno
3
@DanPantry, objek data sederhana jarang perlu diejek, jadi biasanya tidak perlu antarmuka.
David Arno
2
Dalam C♯, antarmuka mendefinisikan Objek, kelas mendefinisikan Jenis Data Abstrak. (Ini adalah kesalahpahaman umum bahwa kelas adalah apa yang Anda gunakan di C♯ untuk menulis kode berorientasi objek. Sebaliknya: kode OO di C♯ tidak pernah dapat menggunakan kelas atau struct sebagai tipe, hanya antarmuka. Segera setelah Anda menggunakan kelas sebagai tipe , Anda tidak lagi melakukan OO.) Jadi, saya tidak akan menyebut tidak menggunakan antarmuka kesalahan desain. Ini adalah pilihan desain: ADT dan Objek memiliki kekuatan dan kelemahan ganda. Anda harus memilih apa yang lebih masuk akal di domain khusus Anda.
Jörg W Mittag
5
@ JörgWMittag: As soon as you use classes as types, you are no longer doing OO - Kelas adalah tipe. Apa?? Warisan kelas normal adalah OO, tidak peduli apa yang Anda katakan. Anda tidak dapat memutuskan bahwa itu bukan OO lagi hanya karena DI adalah hal yang paling disukai sekarang.
Robert Harvey