Dalam C # (dan banyak bahasa lainnya) sangat sah untuk mengakses bidang pribadi dari instance lain dari jenis yang sama. Sebagai contoh:
public class Foo
{
private bool aBool;
public void DoBar(Foo anotherFoo)
{
if (anotherFoo.aBool) ...
}
}
Karena spesifikasi C # (bagian 3.5.1, 3.5.2) menyatakan akses ke bidang pribadi menggunakan tipe, bukan instance. Saya telah mendiskusikan hal ini dengan seorang kolega dan kami mencoba untuk menemukan alasan mengapa ia bekerja seperti ini (daripada membatasi akses ke contoh yang sama).
Argumen terbaik yang bisa kami ajukan adalah untuk pemeriksaan kesetaraan di mana kelas mungkin ingin mengakses bidang pribadi untuk menentukan kesetaraan dengan contoh lain. Apakah ada alasan lain? Atau alasan emas yang benar-benar berarti harus bekerja seperti ini atau sesuatu yang sama sekali tidak mungkin?
Jawaban:
Saya pikir salah satu alasan ia bekerja dengan cara ini adalah karena pengubah akses bekerja pada waktu kompilasi . Dengan demikian, menentukan apakah objek yang diberikan juga merupakan objek saat ini tidak mudah dilakukan. Sebagai contoh, pertimbangkan kode ini:
Bisakah kompiler mencari tahu itu
other
sebenarnyathis
? Tidak dalam semua kasus. Seseorang dapat berargumen bahwa ini seharusnya tidak dikompilasi, tetapi itu berarti kita memiliki jalur kode di mana anggota instance pribadi dari instance yang benar tidak dapat diakses, yang saya pikir bahkan lebih buruk.Hanya membutuhkan jenis-tingkat daripada objek-tingkat visibilitas memastikan bahwa masalahnya adalah penurut, serta membuat situasi yang tampaknya seperti itu harus bekerja benar-benar bekerja.
EDIT : Poin Danilel Hilgarth bahwa alasan ini mundur memang pantas. Desainer bahasa dapat membuat bahasa yang mereka inginkan, dan penulis kompiler harus menyesuaikannya. Yang sedang berkata, perancang bahasa memang memiliki beberapa insentif untuk membuatnya lebih mudah bagi penulis kompiler untuk melakukan pekerjaan mereka. (Meskipun dalam kasus ini, cukup mudah untuk berpendapat bahwa anggota pribadi hanya dapat diakses melalui
this
(baik secara implisit atau eksplisit)).Namun, saya percaya itu membuat masalah lebih membingungkan daripada yang seharusnya. Sebagian besar pengguna (termasuk saya sendiri) akan merasa unneccessarily membatasi jika kode di atas tidak bekerja: setelah semua, itu saya data yang saya sedang mencoba untuk mengakses! Mengapa saya harus melalui
this
?Singkatnya, saya pikir saya mungkin telah melebih-lebihkan kasus untuk itu menjadi "sulit" untuk kompiler. Apa yang sebenarnya ingin saya sampaikan adalah bahwa situasi di atas tampak seperti situasi yang diinginkan oleh para desainer.
sumber
this.bar = 2
tetapi tidakother.bar = 2
, karenaother
bisa menjadi contoh yang berbeda.Karena tujuan dari jenis enkapsulasi yang digunakan dalam bahasa C # dan yang serupa * adalah untuk mengurangi ketergantungan timbal balik dari berbagai potongan kode (kelas dalam C # dan Java), bukan objek yang berbeda dalam memori.
Misalnya, jika Anda menulis kode di satu kelas yang menggunakan beberapa bidang di kelas lain, maka kelas-kelas ini sangat erat digabungkan. Namun, jika Anda berurusan dengan kode di mana Anda memiliki dua objek dari kelas yang sama, maka tidak ada ketergantungan tambahan. Kelas selalu bergantung pada dirinya sendiri.
Namun, semua teori tentang enkapsulasi ini gagal segera setelah seseorang membuat properti (atau mendapatkan / mengatur pasangan di Jawa) dan memaparkan semua bidang secara langsung, yang membuat kelas digabungkan seolah-olah mereka tetap mengakses bidang.
* Untuk klarifikasi tentang jenis enkapsulasi, lihat jawaban Habel yang luar biasa.
sumber
get/set
metode disediakan. Metode Accessor masih mempertahankan abstraksi (pengguna tidak perlu tahu ada bidang di belakangnya, atau bidang apa yang mungkin ada di belakang properti). Misalnya, jika kita sedang menulis sebuahComplex
kelas, kita dapat mengekspos properti untuk mendapatkan / mengatur koordinat kutub, dan yang lain mendapatkan / set untuk koordinat Cartesian. Yang mana yang digunakan kelas di bawahnya tidak diketahui oleh pengguna (bahkan bisa jadi sesuatu yang sama sekali berbeda).private
. Anda dapat memeriksa jawaban saya untuk pandangan saya tentang beberapa hal jika Anda suka;).Cukup banyak jawaban telah ditambahkan ke utas yang menarik ini, namun, saya tidak cukup menemukan alasan sebenarnya mengapa perilaku ini seperti itu. Biarkan saya mencobanya:
Kembali pada hari-hari
Di suatu tempat antara Smalltalk di 80-an dan Jawa pada pertengahan 90-an konsep orientasi objek matang. Penyembunyian informasi, yang awalnya tidak dianggap sebagai konsep yang hanya tersedia untuk OO (disebutkan pertama kali pada tahun 1978), diperkenalkan di Smalltalk karena semua data (bidang) suatu kelas bersifat pribadi, semua metode bersifat publik. Selama banyak perkembangan baru OO di tahun 90-an, Bertrand Meyer mencoba memformalkan banyak konsep OO dalam bukunya landmark Object Oriented Software Construction (OOSC) yang sejak saat itu dianggap sebagai referensi (hampir) definitif pada konsep OO dan desain bahasa. .
Dalam kasus visibilitas pribadi
Menurut Meyer, suatu metode harus tersedia untuk kelas-kelas tertentu (halaman 192-193). Ini jelas memberikan granularitas yang sangat tinggi dari penyembunyian informasi, fitur berikut tersedia untuk classA dan classB dan semua turunannya:
Dalam kasus
private
ia mengatakan yang berikut: tanpa secara eksplisit menyatakan tipe yang terlihat oleh kelasnya sendiri, Anda tidak dapat mengakses fitur (metode / bidang) dalam panggilan yang memenuhi syarat. Yaitu jikax
variabel,x.doSomething()
tidak diizinkan. Akses tanpa pengecualian diizinkan, tentu saja, di dalam kelas itu sendiri.Dengan kata lain: untuk mengizinkan akses oleh instance dari kelas yang sama, Anda harus mengizinkan metode akses oleh kelas itu secara eksplisit. Ini kadang-kadang disebut instance-private versus class-private.
Instance-private dalam bahasa pemrograman
Saya tahu setidaknya ada dua bahasa yang saat ini digunakan yang menggunakan penyembunyian informasi instance-privat sebagai lawan penyembunyian informasi kelas-privat. Salah satunya adalah Eiffel, bahasa yang dirancang oleh Meyer, yang membawa OO ke titik ekstrem. Yang lainnya adalah Ruby, bahasa yang jauh lebih umum saat ini. Di Ruby,
private
berarti: "pribadi ke instance ini" .Pilihan untuk desain bahasa
Telah disarankan bahwa mengizinkan instance-private akan sulit untuk kompiler. Saya rasa tidak, karena relatif mudah untuk hanya mengizinkan atau melarang panggilan yang memenuhi syarat untuk metode. Jika untuk metode pribadi,
doSomething()
diizinkan danx.doSomething()
tidak, perancang bahasa telah secara efektif menentukan aksesibilitas hanya-instance untuk metode dan bidang pribadi.Dari sudut pandang teknis, tidak ada alasan untuk memilih satu cara atau yang lain (terutama ketika mempertimbangkan bahwa Eiffel.NET dapat melakukan ini dengan IL, bahkan dengan banyak pewarisan, tidak ada alasan yang melekat untuk tidak menyediakan fitur ini).
Tentu saja, ini masalah selera dan seperti yang telah disebutkan sebelumnya, beberapa metode mungkin lebih sulit untuk ditulis tanpa fitur visibilitas tingkat kelas dari metode dan bidang pribadi.
Mengapa C # hanya memungkinkan enkapsulasi kelas dan bukan enkapsulasi instance
Jika Anda melihat utas internet pada enkapsulasi instan (istilah yang kadang-kadang digunakan untuk merujuk pada fakta bahwa suatu bahasa mendefinisikan pengubah akses pada tingkat instance, sebagai lawan dari level kelas), konsep ini sering disukai. Namun, mengingat bahwa beberapa bahasa modern menggunakan enkapsulasi instance, setidaknya untuk pengubah akses pribadi, membuat Anda berpikir itu bisa dan sedang digunakan di dunia pemrograman modern.
Namun, C # telah diakui tampak paling sulit di C ++ dan Java untuk desain bahasanya. Sementara Eiffel dan Modula-3 juga ada dalam gambar, mengingat banyak fitur Eiffel yang hilang (multiple inheritance) saya percaya mereka memilih rute yang sama seperti Java dan C ++ ketika datang ke pengubah akses pribadi.
Jika Anda benar-benar ingin tahu mengapa Anda harus mencoba menghubungi Eric Lippert, Krzysztof Cwalina, Anders Hejlsberg atau siapa pun yang bekerja pada standar C #. Sayangnya, saya tidak dapat menemukan catatan definitif dalam Bahasa Pemrograman C # yang beranotasi .
sumber
Foo
secara efektif mewakili antarmuka dan kelas pelaksana; karena COM tidak memiliki konsep referensi objek-kelas - hanya referensi antarmuka - tidak ada cara kelas dapat memegang referensi ke sesuatu yang dijamin menjadi contoh lain dari kelas itu. Desain berbasis antarmuka ini membuat beberapa hal rumit, tetapi itu berarti seseorang dapat merancang kelas yang dapat menggantikan yang lain tanpa harus berbagi internal yang sama.Ini hanya pendapat saya, tetapi secara pragmatis, saya berpikir bahwa jika seorang programmer memiliki akses ke sumber kelas, Anda dapat mempercayai mereka dengan mengakses anggota pribadi instance kelas. Mengapa mengikat programer dengan tangan kanan ketika di sebelah kiri Anda telah memberi mereka kunci kerajaan?
sumber
Alasannya memang adalah pemeriksaan kesetaraan, perbandingan, kloning, kelebihan operator ... Akan sangat sulit untuk mengimplementasikan operator + pada bilangan kompleks, misalnya.
sumber
readonly
dan kemudian itu berlaku untuk instance mendeklarasikan juga. Jelas Anda menegaskan bahwa harus ada aturan kompiler khusus untuk melarang hal ini, tetapi setiap aturan tersebut adalah fitur yang harus ditentukan oleh tim C #, didokumentasi, dilokalkan, diuji, dipelihara, dan didukung. Jika tidak ada manfaat yang menarik, mengapa mereka melakukannya?Pertama-tama, apa yang akan terjadi pada anggota statis pribadi? Apakah hanya dapat diakses dengan metode statis? Anda tentu tidak ingin itu, karena Anda tidak akan dapat mengakses
const
.Mengenai pertanyaan eksplisit Anda, pertimbangkan kasus a
StringBuilder
, yang diimplementasikan sebagai daftar instance dari tautan itu sendiri:Jika Anda tidak dapat mengakses anggota pribadi dari instance lain dari kelas Anda sendiri, Anda harus menerapkan
ToString
seperti ini:Ini akan berhasil, tetapi ini O (n ^ 2) - tidak terlalu efisien. Bahkan, itu mungkin mengalahkan seluruh tujuan memiliki
StringBuilder
kelas di tempat pertama. Jika Anda dapat mengakses anggota pribadi dari instance lain dari kelas Anda sendiri, Anda dapat menerapkanToString
dengan membuat string dengan panjang yang sesuai dan kemudian melakukan salinan yang tidak aman dari setiap chunk ke tempat yang sesuai dalam string:Implementasi ini adalah O (n), yang membuatnya sangat cepat, dan hanya mungkin jika Anda memiliki akses ke anggota pribadi dari instance lain dari kelas Anda .
sumber
internal
membuatnya kurang pribadi! Anda akhirnya mengekspos internal kelas Anda ke segala sesuatu di majelisnya.Ini sangat sah dalam banyak bahasa (C ++ untuk satu). Pengubah akses berasal dari prinsip enkapsulasi dalam OOP. Idenya adalah untuk membatasi akses ke luar , dalam hal ini di luar adalah kelas-kelas lain. Setiap kelas bersarang di C # misalnya dapat mengakses orang tua itu anggota pribadi juga.
Meskipun ini adalah pilihan desain untuk perancang bahasa. Pembatasan akses ini dapat menyulitkan beberapa skenario yang sangat umum sangat tanpa berkontribusi banyak pada isolasi entitas.
Ada diskusi serupa di sini
sumber
Saya tidak berpikir bahwa ada alasan mengapa kami tidak dapat menambahkan tingkat privasi lainnya, di mana data bersifat pribadi untuk setiap contoh. Bahkan, itu mungkin bahkan memberikan perasaan yang baik tentang kelengkapan bahasa.
Tetapi dalam praktik yang sebenarnya, saya ragu itu akan sangat berguna. Seperti yang telah Anda tunjukkan, privasi pribadi kami berguna untuk hal-hal seperti pemeriksaan kesetaraan, serta sebagian besar operasi lainnya yang melibatkan banyak contoh Tipe. Meskipun, saya juga menyukai poin Anda tentang menjaga abstraksi data, karena itu adalah poin penting dalam OOP.
Saya pikir semua di sekitar, menyediakan kemampuan untuk membatasi akses sedemikian rupa bisa menjadi fitur yang bagus untuk ditambahkan ke OOP. Apakah ini benar-benar bermanfaat? Saya akan mengatakan tidak, karena sebuah kelas harus dapat mempercayai kodenya sendiri. Karena kelas itu adalah satu-satunya hal yang dapat mengakses anggota pribadi, tidak ada alasan nyata untuk membutuhkan abstraksi data ketika berhadapan dengan instance kelas lain.
Tentu saja, Anda selalu dapat menulis kode seolah-olah pribadi diterapkan pada instance. Gunakan
get/set
metode biasa untuk mengakses / mengubah data. Itu mungkin akan membuat kode lebih mudah dikelola jika kelas dapat mengalami perubahan internal.sumber
Jawaban bagus diberikan di atas. Saya akan menambahkan bahwa bagian dari masalah ini adalah fakta bahwa instantiate kelas di dalam dirinya sendiri bahkan diperbolehkan di tempat pertama. Itu membuat sejak dalam logika rekursif "untuk" loop, misalnya, untuk menggunakan jenis trik selama Anda memiliki logika untuk mengakhiri rekursi. Tetapi instantiate atau melewati kelas yang sama di dalam dirinya sendiri tanpa membuat loop seperti itu secara logis menciptakan bahayanya sendiri, meskipun paradigma pemrogramannya diterima secara luas. Sebagai contoh, Kelas C # dapat instantiate salinan itu sendiri di konstruktor default, tetapi itu tidak melanggar aturan atau membuat loop sebab akibat. Mengapa?
BTW .... masalah yang sama ini berlaku untuk anggota yang "dilindungi" juga. :(
Saya tidak pernah menerima paradigma pemrograman sepenuhnya karena masih datang dengan serangkaian masalah dan risiko yang sebagian besar programmer tidak sepenuhnya pahami sampai masalah seperti masalah ini muncul dan membingungkan orang dan menentang seluruh alasan untuk memiliki anggota pribadi.
Aspek "aneh dan aneh" dari C # ini adalah satu lagi alasan mengapa pemrograman yang baik tidak ada hubungannya dengan pengalaman dan keterampilan, tetapi hanya mengetahui trik dan jebakan ..... seperti bekerja pada mobil. Argumennya bahwa aturan dimaksudkan untuk dilanggar, yang merupakan model yang sangat buruk untuk bahasa komputasi apa pun.
sumber
Sepertinya saya bahwa jika data bersifat pribadi untuk contoh lain dari jenis yang sama, itu tidak akan menjadi jenis yang sama lagi. Tampaknya tidak akan berperilaku atau bertindak sama dengan contoh lainnya. Perilaku dapat dengan mudah dimodifikasi berdasarkan data internal pribadi itu. Itu hanya akan menyebarkan kebingungan menurut pendapat saya.
Secara longgar, saya pribadi berpikir bahwa menulis kelas yang berasal dari kelas dasar menawarkan fungsionalitas serupa yang Anda gambarkan dengan 'memiliki data pribadi per instance'. Sebagai gantinya, Anda hanya memiliki definisi kelas baru per jenis 'unik'.
sumber