... Karena ini memaksa kita untuk membuat getter dan setter, yang dalam praktiknya sering sama sekali asing? Apakah ada alasan desain bahasa yang baik mengapa antarmuka di sebagian besar (semua?) Bahasa tidak memungkinkan bidang anggota untuk membantu memenuhi spesifikasi antarmuka?
Dalam sebagian besar bahasa yang ada, anggota diperlakukan secara fundamental berbeda dari metode, tetapi apakah ini HARUS terjadi? Dalam bahasa teoretis, tidak bisakah kita memiliki antarmuka yang (a) menetapkan metode zero-arg sebagai pengambil, tetapi menerima anggota yang dapat dibaca sebagai implementor, dan (b) menetapkan metode satu-arg sebagai penyetel, tetapi menerima bidang anggota yang dapat ditulis sebagai implementornya, diberikan bahasa yang mendukung penetapan kontrol akses langsung pada variabel ketika dideklarasikan? Bahasa prototipe yang memungkinkan pewarisan berganda (untuk membahas poin yang sangat valid yang dibuat beberapa responden), akan ideal untuk hal seperti ini.
Tolong beri tahu saya jika sesuatu seperti ini sudah ada.
Jawaban:
Bukan untuk alasan yang didasarkan pada teori. Saya pikir alasan mengapa Anda tidak sering melihatnya praktis:
Yang pertama adalah bagaimana Anda menangani hal-hal ini saat runtime. Di Java atau C ++, penugasan kepada anggota publik yang diketik primitif seperti
foo.x = 5
mudah untuk ditangani: barang5
- barang ke dalam register, mencari tahu di mana dalam memorix
anggotafoo
kehidupan dan membuang register di sana. Dalam bahasa seperti C # di mana kelas dapat merangkum tugas pencegatan kepada anggota atau pra-menghitung bagaimana anggota akan mengevaluasi , kompiler tidak akan memiliki prioritaspengetahuan tentang apakah implementasi kelas yang disediakan saat runtime menggunakan fitur itu. Itu berarti semua penugasan anggota publik dan evaluasi anggota publik harus ditunda untuk implementasi. * Operasi yang melibatkan banyak akses ke anggota tersebut akhirnya membuat hit kinerja karena mereka terus melakukan panggilan subrutin yang dapat ditangani lebih cepat dengan teratur tugas in-line. Meninggalkan evaluasi dan penugasan terpisah dari getter dan setter memberi pengembang kontrol atas bagaimana mereka ditangani dalam situasi tertentu.Yang kedua adalah usia. Dengan pengecualian yang mungkin untuk F #, tidak ada bahasa yang digunakan secara luas saat ini lebih muda dari satu dekade, dan bahkan baru-baru ini, siklus tambahan yang diperlukan untuk membuat evaluasi dan penugasan yang mungkin dilakukan mungkin tidak dapat diterima.
Ketiga adalah inersia. Saya pikir sampai tingkat tertentu, masih ada beberapa stigma yang terkait dengan melakukan penugasan langsung kepada anggota publik bahkan jika bahasa tersebut memungkinkan kelas untuk memanipulasi mereka dan orang-orang masih berpikir dalam hal getter dan setter menjadi lebih baik. Itu akan hilang dengan waktu, seperti halnya dengan semua orang yang memiliki hidung di udara di atas orang-orang yang menulis program dalam bahasa tingkat yang lebih tinggi daripada berkumpul.
* Ada tradeoff yang datang dengan ini: bahasa harus pergi seluruh babi dengan menunda evals / tugas ke kelas atau menolak saat runtime jika kode menggunakan in-line evals / tugas untuk kelas tertentu mencoba untuk menghubungkan dengan implementasi yang memotong mereka . Secara pribadi, saya pikir yang terakhir adalah ide yang buruk karena memaksa kompilasi ulang kode yang menggunakan API yang tidak berubah.
sumber
Ya, benar.
Bidang anggota adalah data per-instance. Data harus benar-benar ada di suatu tempat di memori untuk setiap contoh - harus ada alamat atau urutan alamat yang disediakan untuk itu. Di Java, .NET, dan banyak bahasa OO lainnya, data ini terus bertambah.
Metode adalah data per kelas. Mereka adalah pointer ke tabel. Begitulah cara mendapatkan pengiriman virtual. Sebenarnya hanya ada satu instance yang dialokasikan, per kelas.
Antarmuka pada dasarnya adalah tipe kelas khusus yang hanya berisi metode, tidak ada data instan. Itu bagian dari definisi antarmuka. Jika itu berisi data apa pun, itu tidak akan menjadi antarmuka lagi, itu akan menjadi kelas abstrak. Kedua pilihan itu baik-baik saja, tentu saja, tergantung pada pola desain seperti apa yang Anda coba terapkan. Tetapi jika Anda menambahkan data instan ke antarmuka, Anda akan mengambil semua yang membuatnya antarmuka.
OK, jadi mengapa metode antarmuka tidak bisa menunjuk ke bidang anggota? Karena tidak ada apa-apa di tingkat kelas untuk menunjuk. Sekali lagi, sebuah antarmuka tidak lain hanyalah sebuah tabel metode. Kompiler hanya dapat menghasilkan satu tabel, dan setiap metode di dalamnya harus mengarah ke alamat memori yang sama. Jadi tidak mungkin metode antarmuka untuk menunjuk ke bidang kelas, karena bidang kelas adalah alamat yang berbeda untuk setiap contoh.
Mungkin kompiler dapat menerima ini dan menghasilkan metode pengambil untuk antarmuka untuk menunjuk ke - tetapi kemudian pada dasarnya menjadi properti . Jika Anda ingin tahu mengapa Java tidak memiliki properti, yah ... itu adalah kisah yang sangat panjang dan membosankan yang saya lebih suka tidak masuki di sini. Tapi jika Anda tertarik, Anda mungkin ingin melihat pada bahasa OO lain yang melakukan menerapkan sifat, seperti C # atau Delphi. Sintaksnya sangat mudah:
Yap, hanya itu yang ada untuk itu. Penting: Ini bukan bidang, "int Bar" di
Foo
kelas adalah Properti Otomatis , dan kompiler melakukan persis seperti yang saya jelaskan di atas: secara otomatis menghasilkan getter dan setter (serta bidang anggota dukungan).Jadi, rekap jawaban untuk pertanyaan Anda:
sumber
ini adalah hasil dari penyembunyian informasi, yaitu anggota tersebut tidak boleh terlihat sebagai bidang tetapi sebagai properti yang mungkin atau mungkin tidak disimpan / di-cache secara lokal
salah satu contoh adalah kode hash itu berubah ketika objek berubah sehingga lebih baik untuk menghitungnya ketika diminta dan tidak setiap kali objek berubah
selain itu sebagian besar bahasa adalah single-inheritance + multi-implement dan memungkinkan bidang untuk menjadi bagian dari antarmuka menempatkan Anda ke dalam wilayah multi-inheritance (satu-satunya hal yang hilang adalah implementasi fungsi default) yang merupakan ladang ranjau kasus tepi untuk mendapatkan yang benar
sumber
Variabel anggota adalah implementasi kelas, bukan antarmuka. Anda mungkin ingin mengubah implementasi, jadi kelas lain tidak boleh diizinkan untuk merujuk ke implementasi itu secara langsung.
Pertimbangkan kelas dengan metode berikut - Sintaks C ++, tapi itu tidak penting ...
Tampaknya cukup jelas bahwa akan ada variabel anggota yang dipanggil
mode
ataum_mode
serupa yang secara langsung menyimpan nilai mode itu, melakukan cukup banyak apa yang "properti" akan lakukan dalam beberapa bahasa - tetapi itu tidak selalu benar. Ada banyak cara berbeda yang bisa ditangani. Misalnya, metode Set_Mode mungkin ...Setelah melakukan itu, banyak metode lain dari kelas contoh kemudian dapat memanggil metode yang sesuai dari kelas internal melalui pointer, mendapatkan perilaku mode khusus tanpa perlu memeriksa apa mode saat ini.
Intinya di sini kurang bahwa ada lebih dari satu cara yang mungkin untuk menerapkan hal semacam ini, dan lebih banyak lagi bahwa pada suatu waktu Anda mungkin berubah pikiran. Mungkin Anda mulai dengan variabel sederhana, tetapi semua pernyataan peralihan untuk mengidentifikasi mode dalam setiap metode semakin menyebalkan. Atau mungkin Anda memulai dengan hal instance-pointer, tetapi itu ternyata terlalu berat untuk situasi Anda.
Ternyata hal-hal seperti ini dapat terjadi untuk data anggota apa pun. Anda mungkin perlu mengubah unit tempat nilai disimpan dari mil ke kilometer, atau Anda mungkin menemukan bahwa pencacahan nomor identifikasi-unik Anda tidak lagi dapat secara unik mengidentifikasi semua kasing tanpa mempertimbangkan beberapa informasi tambahan, atau apa pun.
Ini juga dapat terjadi untuk metode - beberapa metode murni untuk penggunaan internal, tergantung pada implementasinya, dan harus bersifat pribadi. Tetapi banyak metode adalah bagian dari antarmuka, dan tidak perlu diganti namanya atau apa pun hanya karena implementasi internal kelas telah diganti.
Bagaimanapun, jika implementasi Anda diekspos, kode lain pasti akan mulai bergantung padanya, dan Anda akan dikunci untuk menjaga implementasi itu. Jika implementasi Anda disembunyikan dari kode lain, situasi itu tidak dapat terjadi.
Memblokir akses ke detail implementasi disebut "data hiding".
sumber
Seperti yang orang lain catat, sebuah antarmuka menggambarkan apa yang dilakukan kelas, bukan bagaimana melakukannya. Ini pada dasarnya sebuah kontrak yang harus dipenuhi oleh kelas yang mewarisi.
Apa yang Anda jelaskan sedikit berbeda - kelas yang menyediakan beberapa implementasi tetapi mungkin tidak semua, sebagai bentuk penggunaan kembali. Ini umumnya dikenal sebagai kelas abstrak dan didukung dalam sejumlah bahasa (misalnya C ++ dan Java). Kelas-kelas ini tidak dapat di-instantiate dengan hak mereka sendiri, mereka hanya bisa diwarisi oleh kelas-kelas konkret (atau lebih lanjut abstrak).
Sekali lagi seperti yang sudah disebutkan, kelas-kelas abstrak cenderung memiliki nilai paling besar dalam bahasa-bahasa yang mendukung multiple-inheritance karena mereka dapat digunakan untuk menyediakan bagian-bagian fungsionalitas tambahan untuk kelas yang lebih besar. Namun, ini sering dianggap gaya buruk, karena umumnya hubungan agregasi ("memiliki-a") akan lebih tepat
sumber
Properti Objective-C menyediakan fungsionalitas semacam ini dengan aksesor properti yang disintesis. Properti Objective-C pada dasarnya hanya janji bahwa kelas menyediakan accessors, biasanya dari bentuk
foo
untuk pengambil dansetFoo:
untuk penyetel. Deklarasi properti menentukan atribut tertentu dari accessors yang terkait dengan manajemen memori dan perilaku threading. Ada juga@synthesize
arahan yang memberi tahu kompiler untuk menghasilkan pengakses dan bahkan variabel instance terkait, jadi Anda tidak perlu repot menulis getter dan setter atau mendeklarasikan sebuah ivar untuk setiap properti. Ada juga beberapa gula sintaksis yang membuat akses properti terlihat seperti mengakses bidang struct, tetapi sebenarnya menghasilkan panggilan pengakses properti.Hasil dari semua itu adalah bahwa saya dapat mendeklarasikan kelas dengan properti:
dan kemudian saya dapat menulis kode yang terlihat seperti mengakses ivars secara langsung, tetapi sebenarnya menggunakan metode accessor:
Sudah dikatakan bahwa pengakses memungkinkan Anda memisahkan antarmuka dari implementasi, dan alasan yang ingin Anda lakukan adalah untuk menjaga kemampuan Anda untuk mengubah implementasi di masa depan tanpa mempengaruhi kode klien. Kekurangannya adalah Anda harus cukup disiplin untuk menulis dan menggunakan pengakses hanya untuk menjaga opsi masa depan Anda terbuka. Objective-C membuat menghasilkan dan menggunakan accessor semudah menggunakan ivars secara langsung. Bahkan, karena mereka menangani banyak pekerjaan manajemen memori, menggunakan properti sebenarnya jauh lebih mudah daripada mengakses gars secara langsung. Dan ketika saatnya tiba bahwa Anda ingin mengubah implementasi Anda, Anda selalu dapat menyediakan accessors Anda sendiri daripada membiarkan kompiler menghasilkannya untuk Anda.
sumber
Bahasa "Terbanyak"? Bahasa apa yang telah Anda selidiki? Dalam bahasa dinamis tidak ada antarmuka. Hanya ada tiga atau empat bahasa OO yang diketik secara statis dengan popularitas apa pun: Java, C #, Objective-C, dan C ++. C ++ tidak memiliki antarmuka; alih-alih kami menggunakan kelas abstrak, dan kelas abstrak dapat berisi anggota data publik. C # dan Objective-C keduanya mendukung properti, sehingga tidak perlu getter dan setter dalam bahasa tersebut. Hanya menyisakan Jawa. Agaknya satu-satunya alasan Java tidak mendukung properti adalah karena sulit untuk memperkenalkannya dengan cara yang kompatibel dengan terbelakang.
sumber
Beberapa jawaban menyebutkan 'menyembunyikan informasi' dan 'detail implementasi' karena alasan metode antarmuka lebih disukai daripada bidang. Saya pikir alasan utamanya lebih sederhana dari itu.
Antarmuka adalah fitur bahasa yang memungkinkan Anda untuk bertukar perilaku dengan meminta beberapa kelas mengimplementasikan antarmuka. Perilaku kelas ditentukan oleh metodenya. Fields, di sisi lain, hanya mencerminkan keadaan suatu objek.
Tentu saja, bidang atau properti kelas mungkin menjadi bagian dari perilaku, tetapi mereka bukan bagian penting dari kontrak yang ditentukan oleh antarmuka; mereka hanya hasil. Itulah sebabnya misalnya C # memungkinkan Anda untuk mendefinisikan properti dalam sebuah antarmuka, dan di Jawa Anda mendefinisikan pengambil dalam antarmuka. Di sinilah sedikit informasi yang disembunyikan berperan, tetapi itu bukan poin utama.
Seperti yang saya katakan sebelumnya, antarmuka memungkinkan Anda untuk bertukar perilaku, tapi mari kita lihat bagian bertukar. Antarmuka memungkinkan Anda untuk mengubah implementasi yang mendasarinya dengan membuat kelas implementasi baru. Jadi memiliki antarmuka yang terdiri dari bidang tidak masuk akal, karena Anda tidak bisa banyak berubah tentang implementasi bidang.
Satu-satunya alasan Anda ingin membuat antarmuka bidang saja adalah ketika Anda ingin mengatakan bahwa beberapa kelas adalah jenis objek tertentu. Saya telah menekankan bagian 'adalah', karena Anda harus menggunakan warisan daripada antarmuka.
Catatan: Saya tahu bahwa komposisi yang lebih disukai daripada warisan dan komposisi biasanya hanya dapat dicapai dengan menggunakan antarmuka, tetapi menggunakan antarmuka hanya untuk 'menandai' kelas dengan beberapa properti sama buruknya. Warisan sangat bagus jika digunakan dengan tepat, dan selama Anda tetap berpegang pada Prinsip Tanggung Jawab Tunggal, Anda seharusnya tidak memiliki masalah.
Maksud saya, saya tidak pernah bekerja dengan perpustakaan kontrol UI misalnya, yang tidak menggunakan warisan. Satu-satunya area di mana antarmuka berperan di sini adalah dalam penyediaan data, misalnya
ITableDataAdapter
untuk mengikat data ke kontrol tabel. Hal ini persis jenis hal interface dimaksudkan untuk, untuk memiliki perilaku dipertukarkan .sumber
Di Jawa alasannya adalah bahwa antarmuka hanya memungkinkan metode yang ditentukan .
Oleh karena itu jika Anda menginginkan sesuatu dengan bidang dalam kontrak antarmuka, Anda perlu melakukannya sebagai metode pengambil dan penyetel.
Lihat juga pertanyaan ini: Kapan Getters and Setters dibenarkan
sumber
Inti dari antarmuka adalah menentukan fungsi yang harus Anda terapkan untuk mengklaim mendukung antarmuka ini.
sumber
Anggota tidak diperbolehkan dalam antarmuka karena bahasa-bahasa itu harus berurusan dengan kejahatan yang menghebohkan, pewarisan berganda.
sumber