Java membuat perbedaan yang jelas antara class
dan interface
. (Saya percaya C # juga, tapi saya tidak punya pengalaman dengannya). Namun ketika menulis C ++ tidak ada perbedaan yang dipaksakan bahasa antara kelas dan antarmuka.
Akibatnya saya selalu melihat antarmuka sebagai solusi untuk kurangnya pewarisan berganda di Jawa. Membuat perbedaan seperti itu terasa sewenang-wenang dan tidak berarti dalam C ++.
Saya selalu cenderung pergi dengan pendekatan "menulis hal-hal dengan cara yang paling jelas", jadi jika dalam C ++ saya punya apa yang disebut antarmuka di Jawa, misalnya:
class Foo {
public:
virtual void doStuff() = 0;
~Foo() = 0;
};
dan saya kemudian memutuskan bahwa sebagian besar pelaksana Foo
ingin berbagi beberapa fungsi umum yang mungkin akan saya tulis:
class Foo {
public:
virtual void doStuff() = 0;
~Foo() {}
protected:
// If it needs this to do its thing:
int internalHelperThing(int);
// Or if it doesn't need the this pointer:
static int someOtherHelper(int);
};
Yang kemudian membuat ini bukan antarmuka dalam arti Java lagi.
Sebaliknya C ++ memiliki dua konsep penting, terkait dengan masalah warisan yang mendasari yang sama:
virtual
warisanKelas tanpa variabel anggota tidak dapat menempati ruang tambahan saat digunakan sebagai basis
"Subobjek kelas dasar mungkin memiliki ukuran nol"
Di antara yang saya coba hindari # 1 sedapat mungkin - jarang terjadi skenario di mana yang benar-benar merupakan desain "terbersih". Namun # 2 adalah perbedaan yang halus, tetapi penting antara pemahaman saya tentang istilah "antarmuka" dan fitur bahasa C ++. Sebagai hasil dari ini saya saat ini (hampir) tidak pernah menyebut hal-hal sebagai "antarmuka" di C ++ dan berbicara dalam hal kelas dasar dan ukurannya. Saya akan mengatakan bahwa dalam konteks C ++ "antarmuka" adalah nama yang salah.
Namun telah menjadi perhatian saya bahwa tidak banyak orang membuat perbedaan seperti itu.
- Apakah saya akan kehilangan apa pun dengan membiarkan (mis.
protected
)virtual
Fungsi tidak ada dalam "antarmuka" di C ++? (Perasaan saya justru sebaliknya - lokasi yang lebih alami untuk kode bersama) - Apakah istilah "antarmuka" bermakna dalam C ++ - apakah itu menyiratkan hanya murni
virtual
atau apakah adil untuk memanggil kelas C ++ tanpa variabel anggota antarmuka masih?
sumber
internalHelperThing
dapat disimulasikan dalam hampir semua kasus.~Foo() {}
dalam kelas abstrak adalah kesalahan di bawah (hampir) setiap keadaan.Jawaban:
Dalam C ++, istilah "antarmuka" tidak hanya memiliki satu definisi yang diterima secara luas - jadi setiap kali Anda akan menggunakannya, Anda harus mengatakan apa yang Anda maksudkan secara tepat - kelas dasar virtual dengan atau tanpa implementasi default, file header, anggota publik dari kelas yang sewenang-wenang dan sebagainya.
Mengenai contoh Anda: di Jawa (dan serupa di C #), kode Anda mungkin akan menyiratkan pemisahan kekhawatiran:
Di C ++, Anda bisa melakukan ini tetapi Anda tidak dipaksa. Dan jika Anda suka memanggil kelas sebagai antarmuka jika tidak memiliki variabel anggota, tetapi berisi implementasi standar untuk beberapa metode, lakukan saja, tetapi pastikan semua orang yang Anda ajak bicara tahu apa yang Anda maksud.
sumber
public
bagian - bagian dari.h
file.Kedengarannya agak seperti Anda mungkin telah jatuh ke dalam perangkap yang membingungkan makna apa itu bahwa antarmuka secara konseptual baik sebagai implementasi (antarmuka - huruf kecil 'i'), dan abstraksi (Antarmuka - huruf besar 'I' ).
Sehubungan dengan contoh Anda, bit kode pertama Anda hanyalah sebuah kelas. Sementara kelas Anda memiliki antarmuka dalam arti bahwa ia menyediakan metode untuk memungkinkan akses ke perilaku itu, itu bukan Antarmuka dalam arti deklarasi Antarmuka yang menyediakan lapisan abstraksi yang mewakili jenis perilaku yang Anda mungkin ingin kelas untuk melaksanakan. Jawaban Doc Brown untuk posting Anda menunjukkan dengan tepat apa yang saya bicarakan di sini.
Antarmuka sering disebut-sebut sebagai "solusi" untuk bahasa yang tidak mendukung banyak pewarisan, tetapi saya merasa itu lebih merupakan kesalahpahaman daripada kebenaran yang sulit (dan saya curiga saya mungkin akan disalahkan karena menyatakan itu!). Antarmuka benar-benar tidak ada hubungannya dengan pewarisan berganda karena mereka tidak memerlukan pewarisan, atau menjadi leluhur untuk memberikan kompatibilitas fungsional antar kelas, atau abstraksi implementasi untuk kelas. Bahkan, mereka dapat memungkinkan Anda untuk menghapus warisan secara efektif seandainya Anda ingin menerapkan kode Anda dengan cara itu - bukan berarti saya sepenuhnya merekomendasikannya, tetapi saya hanya mengatakan Anda bisalakukan. Jadi kenyataannya adalah bahwa terlepas dari hal-hal warisan, Antarmuka menyediakan sarana dengan mana kelas-jenis dapat didefinisikan, menetapkan aturan yang menentukan bagaimana objek dapat berkomunikasi satu sama lain, sehingga mereka memungkinkan Anda untuk menentukan perilaku yang harus didukung oleh kelas Anda tanpa mendikte metode khusus yang digunakan untuk menerapkan perilaku itu.
Antarmuka murni dimaksudkan untuk sepenuhnya abstrak, karena memungkinkan definisi kontrak kompatibilitas antara kelas yang mungkin tidak selalu mewarisi perilaku mereka dari leluhur yang sama. Saat diimplementasikan, Anda ingin membuat pilihan tentang apakah akan memungkinkan perilaku implementasi diperluas dalam subkelas. Jika metode Anda tidak
virtual
, Anda kehilangan kemampuan untuk memperluas perilaku itu nanti jika Anda memutuskan Anda perlu membuat kelas turunan. Terlepas dari apakah implementasinya virtual atau tidak, Interface mendefinisikan kompatibilitas perilaku di dalam dan dari dirinya sendiri, sementara kelas menyediakan implementasi perilaku itu untuk instance yang diwakili oleh kelas.Maafkan saya, tapi sudah lama sejak saya benar-benar menulis aplikasi serius di C ++. Saya ingat bahwa Interface adalah kata kunci untuk keperluan abstraksi seperti yang saya jelaskan di sini. Saya tidak akan menyebut kelas C ++ dalam bentuk apa pun sebagai Antarmuka, saya lebih suka mengatakan bahwa kelas memiliki antarmuka dalam arti yang telah saya uraikan di atas. Dalam arti istilah IS bermakna, tetapi itu benar-benar tergantung pada konteksnya.
sumber
Antarmuka Java bukanlah "solusi", mereka adalah keputusan desain yang disengaja untuk menghindari beberapa masalah dengan banyak warisan seperti warisan berlian, dan untuk mendorong praktik desain yang meminimalkan penggandengan.
Antarmuka Anda dengan metode yang dilindungi adalah kasus buku teks yang perlu "lebih suka komposisi daripada warisan." Mengutip dari Sutter dan Alexandrescu di bagian dengan nama itu dari Standar C ++ C ++ mereka yang sangat baik :
Dengan memasukkan fungsi pembantu Anda di antarmuka Anda, Anda mungkin menyimpan sedikit pengetikan sekarang, tetapi Anda memperkenalkan kopling yang akan melukai Anda di ujung jalan. Ini hampir selalu lebih baik untuk jangka panjang untuk membuat fungsi pembantu Anda terpisah dan dilewatkan
Foo*
.STL adalah contoh yang cukup bagus dari ini. Fungsi pembantu sebanyak mungkin ditarik keluar
<algorithm>
alih-alih berada di kelas wadah. Misalnya, karenasort()
menggunakan API penampung publik untuk melakukan tugasnya, Anda tahu Anda dapat menerapkan algoritma penyortiran Anda sendiri tanpa harus mengubah kode STL. Desain ini memungkinkan perpustakaan seperti boost, yang meningkatkan STL alih-alih menggantinya, tanpa STL perlu tahu apa-apa tentang boost.sumber
Anda akan berpikir begitu, tetapi menempatkan metode non-virtual yang dilindungi dalam kelas yang abstrak menentukan implementasi bagi siapa saja yang menulis subkelas. Melakukan hal itu mengalahkan tujuan antarmuka dalam arti murni, yaitu untuk menyediakan lapisan yang menyembunyikan apa yang ada di bawahnya.
Ini adalah salah satu contoh di mana tidak ada jawaban satu ukuran untuk semua dan Anda harus menggunakan pengalaman dan penilaian Anda untuk membuat keputusan. Jika Anda dapat mengatakan dengan keyakinan 100% bahwa setiap subkelas yang mungkin dari kelas Anda yang sepenuhnya virtual
Foo
akan selalu membutuhkan implementasi metode yang dilindungibar()
, maka ituFoo
adalah tempat yang tepat untuk itu. Setelah Anda memiliki subkelasBaz
yang tidak perlubar()
, Anda juga harus hidup dengan fakta bahwa tidakBaz
akan memiliki akses ke kode yang seharusnya atau melalui latihan menata ulang hierarki kelas Anda. Yang pertama bukan latihan yang baik dan yang terakhir bisa memakan waktu lebih dari beberapa menit yang diperlukan untuk mengatur hal-hal dengan benar.Bagian 10.4 dari standar C ++ menyebutkan secara singkat penggunaan kelas abstrak untuk mengimplementasikan antarmuka tetapi tidak mendefinisikannya secara formal. Istilah ini bermakna dalam konteks ilmu komputer umum, dan siapa pun yang kompeten harus memahami bahwa "
Foo
adalah antarmuka untuk (apa pun)" menyiratkan beberapa bentuk abstraksi. Orang-orang dengan paparan bahasa dengan konstruksi antarmuka yang didefinisikan mungkin berpikir virtual murni, tetapi siapa pun yang perlu benar-benar bekerja dengannyaFoo
akan melihat definisi sebelum melanjutkan.sumber
baz
selalu dapat memiliki implementasi sepertireturn false;
atau apa pun. Jika metode ini tidak berlaku untuk semua subclass, itu tidak termasuk dalam kelas abstrak dasar dalam bentuk apa pun.