Apa yang harus diizinkan di dalam getter dan setter?

45

Saya masuk ke argumen internet yang menarik tentang metode dan enkapsulasi pengambil dan penyetel. Seseorang mengatakan bahwa semua yang harus mereka lakukan adalah penugasan (setter) atau akses variabel (getter) untuk menjaga mereka "murni" dan memastikan enkapsulasi.

  • Apakah saya benar bahwa ini akan benar-benar mengalahkan tujuan mendapatkan getter dan setter di tempat pertama dan validasi dan logika lainnya (tanpa efek samping yang aneh tentu saja) harus diizinkan?
  • Kapan validasi harus terjadi?
    • Saat mengatur nilai, di dalam setter (untuk melindungi objek agar tidak pernah memasuki kondisi yang tidak valid - pendapat saya)
    • Sebelum mengatur nilai, di luar setter
    • Di dalam objek, sebelum setiap kali nilainya digunakan
  • Apakah penyetel diizinkan untuk mengubah nilai (mungkin mengonversi nilai yang valid ke beberapa representasi internal kanonik)?
Botond Balázs
sumber
18
Hal terbaik tentang getter dan setter adalah kemampuan untuk tidak memilikinya , yaitu cuti dari setter dan Anda memiliki properti read-only, cuti pengambil dan Anda memiliki opsi konfigurasi yang nilainya saat ini bukan urusan siapa-siapa.
Michael Borgwardt
7
@MichaelBorgwardt Tinggalkan keduanya untuk memiliki antarmuka "kirim, jangan tanya" yang bersih. Properties = bau kode potensial.
Konrad Rudolph
2
Setter juga dapat digunakan untuk mengangkat suatu Acara .
John Isaiah Carmona
@KonradRudolph Saya setuju dengan pernyataan Anda, meskipun saya ingin benar-benar menekankan kata "potensial."
Phil

Jawaban:

37

Saya ingat memiliki argumen yang sama dengan dosen saya ketika belajar C ++ di universitas. Saya tidak bisa melihat gunanya menggunakan getter dan setter ketika saya bisa membuat variabel publik. Saya mengerti lebih baik sekarang dengan pengalaman bertahun-tahun dan saya telah belajar alasan yang lebih baik daripada hanya mengatakan "untuk mempertahankan enkapsulasi".

Dengan mendefinisikan getter dan setter, Anda akan memberikan antarmuka yang konsisten sehingga jika Anda ingin mengubah implementasi Anda, Anda cenderung melanggar kode dependen. Ini sangat penting ketika kelas Anda diekspos melalui API dan digunakan di aplikasi lain atau oleh pihak ke-3. Jadi bagaimana dengan hal-hal yang masuk ke pengambil atau penyetel?

Getters pada umumnya lebih baik diimplementasikan sebagai langkah sederhana untuk akses ke nilai karena ini membuat perilaku mereka dapat diprediksi. Saya katakan secara umum, karena saya telah melihat kasus di mana getter telah digunakan untuk mengakses nilai yang dimanipulasi oleh perhitungan atau bahkan oleh kode kondisional. Secara umum tidak begitu baik jika Anda membuat komponen visual untuk digunakan pada waktu desain, tetapi tampaknya berguna saat dijalankan. Namun tidak ada perbedaan nyata antara ini dan menggunakan metode sederhana, kecuali bahwa ketika Anda menggunakan metode, Anda biasanya lebih cenderung untuk menyebutkan metode yang lebih tepat sehingga fungsionalitas "pengambil" lebih jelas ketika membaca kode.

Bandingkan yang berikut ini:

int aValue = MyClass.Value;

dan

int aValue = MyClass.CalculateValue();

Opsi kedua memperjelas bahwa nilai sedang dihitung, sedangkan contoh pertama memberi tahu Anda bahwa Anda hanya mengembalikan nilai tanpa mengetahui apa pun tentang nilai itu sendiri.

Anda mungkin dapat berdebat bahwa yang berikut ini akan lebih jelas:

int aValue = MyClass.CalculatedValue;

Namun masalahnya adalah Anda berasumsi bahwa nilainya telah dimanipulasi di tempat lain. Jadi dalam kasus seorang pengambil, sementara Anda mungkin ingin mengasumsikan bahwa sesuatu yang lain terjadi ketika Anda mengembalikan nilai, sulit untuk membuat hal-hal seperti itu jelas dalam konteks properti, dan nama properti tidak boleh mengandung kata kerja jika tidak, sekilas membuatnya sulit untuk memahami apakah nama yang digunakan harus dihiasi dengan tanda kurung ketika diakses.

Setter adalah kasus yang sedikit berbeda. Sangat sepatutnya bahwa setter memberikan beberapa pemrosesan tambahan untuk memvalidasi data yang dikirimkan ke properti, melemparkan pengecualian jika menetapkan nilai akan melanggar batas-batas yang ditentukan dari properti. Masalah yang dimiliki beberapa pengembang dengan menambahkan pemrosesan ke setter adalah bahwa selalu ada godaan untuk membuat setter melakukan sedikit lagi, seperti melakukan perhitungan atau manipulasi data dalam beberapa cara. Di sinilah Anda bisa mendapatkan efek samping yang dalam beberapa kasus bisa tidak terduga atau tidak diinginkan.

Dalam hal setter saya selalu menerapkan aturan praktis sederhana, yaitu melakukan sesedikit mungkin data. Sebagai contoh, saya biasanya akan mengijinkan pengujian batas, dan pembulatan sehingga saya bisa mengajukan pengecualian jika perlu, atau menghindari pengecualian yang tidak perlu di mana mereka dapat dihindari secara masuk akal. Properti floating point adalah contoh yang baik di mana Anda mungkin ingin membulatkan tempat desimal yang berlebihan untuk menghindari peningkatan pengecualian, sementara masih memungkinkan nilai rentang dimasukkan dengan beberapa tempat desimal tambahan.

Jika Anda menerapkan semacam manipulasi input setter, Anda memiliki masalah yang sama dengan pengambil, bahwa sulit untuk memungkinkan orang lain mengetahui apa yang dilakukan setter dengan hanya memberi nama. Sebagai contoh:

MyClass.Value = 12345;

Apakah ini memberi tahu Anda apa yang akan terjadi pada nilai ketika diberikan kepada setter?

Bagaimana tentang:

MyClass.RoundValueToNearestThousand(12345);

Contoh kedua memberi tahu Anda secara tepat apa yang akan terjadi pada data Anda, sementara yang pertama tidak akan memberi tahu Anda jika nilai Anda akan diubah secara sewenang-wenang. Saat membaca kode, contoh kedua akan jauh lebih jelas dalam tujuan dan fungsinya.

Apakah saya benar bahwa ini akan benar-benar mengalahkan tujuan mendapatkan getter dan setter di tempat pertama dan validasi dan logika lainnya (tanpa efek samping yang aneh tentu saja) harus diizinkan?

Memiliki getter dan setter bukan tentang enkapsulasi demi "kemurnian", tetapi tentang enkapsulasi agar kode dapat dengan mudah di-refactored tanpa mempertaruhkan perubahan pada antarmuka kelas yang jika tidak akan merusak kompatibilitas kelas dengan kode panggilan. Validasi sepenuhnya sesuai dalam setter, namun ada risiko kecil adalah bahwa perubahan validasi dapat memutus kompatibilitas dengan kode panggilan jika kode panggilan bergantung pada validasi yang terjadi dengan cara tertentu. Ini adalah situasi yang umumnya jarang terjadi dan relatif berisiko rendah, tetapi harus diperhatikan demi kelengkapan.

Kapan validasi harus terjadi?

Validasi harus terjadi dalam konteks setter sebelum benar-benar menetapkan nilai. Ini memastikan bahwa jika pengecualian dilemparkan, keadaan objek Anda tidak akan berubah dan berpotensi membatalkan datanya. Saya biasanya merasa lebih baik untuk mendelegasikan validasi ke metode terpisah yang akan menjadi hal pertama yang disebut dalam setter, untuk menjaga kode setter relatif tidak berantakan.

Apakah penyetel diizinkan untuk mengubah nilai (mungkin mengonversi nilai yang valid ke beberapa representasi internal kanonik)?

Dalam kasus yang sangat jarang, mungkin. Secara umum, mungkin lebih baik tidak melakukannya. Ini adalah hal yang sebaiknya diserahkan kepada metode lain.

S.Robins
sumber
re: ubah nilainya, mungkin masuk akal untuk menetapkannya menjadi -1 atau beberapa tanda bendera NULL jika setter tersebut meneruskan nilai ilegal
Martin Beckett
1
Ada beberapa masalah dengan ini. Mengatur nilai secara sewenang-wenang menciptakan efek samping yang disengaja dan tidak jelas. Selain itu, tidak memungkinkan kode panggilan untuk menerima umpan balik yang dapat digunakan untuk menangani data ilegal dengan lebih baik. Ini sangat penting dengan nilai-nilai di tingkat UI. Agar adil, satu pengecualian yang baru saja saya pikirkan adalah jika mengizinkan beberapa format tanggal sebagai input, saat menyimpan tanggal dalam format tanggal / waktu standar. Orang dapat berargumentasi bahwa "efek samping" khusus ini adalah normalisasi data saat validasi, asalkan data input tersebut legal.
S.Robins
Ya, salah satu pembenaran dari seorang setter adalah bahwa ia harus mengembalikan true / false jika nilainya dapat ditetapkan.
Martin Beckett
1
"Properti titik apung adalah contoh yang baik di mana Anda mungkin ingin membulatkan tempat desimal yang berlebihan untuk menghindari menaikkan pengecualian" Dalam keadaan apa memiliki tempat desimal yang terlalu banyak menimbulkan pengecualian?
Mark Byers
2
Saya melihat mengubah nilai ke representasi kanonik sebagai bentuk validasi. Mengawetkan nilai yang hilang (misalnya, tanggal saat ini jika cap waktu hanya berisi waktu) atau menskalakan nilai (.93275 -> 93,28%) untuk memastikan konsistensi internal harus OK, tetapi manipulasi semacam itu harus secara eksplisit disebutkan dalam dokumentasi API , terutama jika mereka adalah idiom yang tidak biasa di dalam API.
TMN
20

Jika pengambil / penyetel hanya mencerminkan nilai maka tidak ada gunanya memilikinya, atau dalam membuat nilai pribadi. Tidak ada yang salah dengan membuat beberapa variabel anggota menjadi publik jika Anda memiliki alasan yang bagus. Jika Anda menulis kelas titik 3d maka memiliki publik .x, .y, .z sangat masuk akal.

Seperti yang dikatakan Ralph Waldo Emerson, "Konsistensi yang bodoh adalah hobgoblin dari pikiran-pikiran kecil, dipuja oleh negarawan kecil dan filsuf dan perancang Jawa."

Getters / setters berguna di mana mungkin ada efek samping, di mana Anda perlu memperbarui variabel internal lainnya, menghitung ulang nilai-nilai yang di-cache dan melindungi kelas dari input yang tidak valid.

Pembenaran biasa bagi mereka, bahwa mereka menyembunyikan struktur internal, umumnya paling tidak berguna. misalnya. Saya memiliki poin-poin ini disimpan sebagai 3 float, saya mungkin memutuskan untuk menyimpannya sebagai string di basis data jauh sehingga saya akan membuat getter / setter untuk menyembunyikannya, seolah-olah Anda bisa melakukan itu tanpa memiliki efek lain pada kode pemanggil.

Martin Beckett
sumber
1
Wow, para desainer Jawa itu ilahi? </jk>
@ Dave - jelas tidak - atau mereka akan berima lebih baik ;-)
Martin Beckett
Apakah valid untuk membuat titik 3d dengan x, y, z semuanya Float.NAN?
Andrew T Finnell
4
Saya tidak setuju dengan poin Anda bahwa "pembenaran biasa" (menyembunyikan struktur internal) adalah properti yang paling tidak berguna dari getter dan setter. Dalam C #, itu pasti berguna untuk membuat properti antarmuka yang struktur dasarnya dapat diubah - misalnya, jika Anda hanya ingin properti tersedia untuk pencacahan, itu jauh lebih baik untuk dikatakan IEnumerable<T>daripada memaksanya untuk sesuatu seperti List<T>. Contoh Anda tentang akses basis data yang menurut saya melanggar tanggung jawab tunggal - mencampurkan representasi model dengan bagaimana hal itu dipertahankan.
Brandon Linton
@AndrewFinnell - itu mungkin cara yang bagus untuk menandai poin sebagai tidak mungkin / tidak valid / dihapus dll
Martin Beckett
8

Prinsip Akses Seragam Meyer: "Semua layanan yang ditawarkan oleh modul harus tersedia melalui notasi yang seragam, yang tidak mengkhianati apakah mereka diimplementasikan melalui penyimpanan atau melalui perhitungan." adalah alasan utama di balik getter / setter, alias Properties.

Jika Anda memutuskan untuk cache atau malas menghitung satu bidang kelas maka Anda dapat mengubahnya kapan saja jika Anda hanya memiliki aksesor properti dan bukan data konkret.

Nilai objek, struktur sederhana tidak memerlukan abstraksi ini, tetapi kelas sepenuhnya matang, menurut saya.

Karl
sumber
2

Strategi umum untuk merancang kelas, yang diperkenalkan melalui bahasa Eiffel, adalah Command-Query Separation . Idenya adalah bahwa metode harus baik memberitahu Anda sesuatu tentang objek atau memberitahu objek untuk melakukan sesuatu, tetapi tidak melakukan keduanya.

Ini hanya berkaitan dengan antarmuka publik kelas, bukan representasi internal. Pertimbangkan objek model data yang didukung oleh baris dalam database. Anda dapat membuat objek tanpa memuat data, maka saat pertama kali Anda memanggil getter, ia benar-benar melakukannya SELECT. Tidak apa-apa, Anda mungkin mengubah beberapa detail internal tentang bagaimana objek diwakili tetapi Anda tidak mengubah tampilannya kepada klien objek itu. Anda harus dapat memanggil getter beberapa kali dan masih mendapatkan hasil yang sama, bahkan jika mereka melakukan pekerjaan yang berbeda untuk mengembalikan hasil tersebut.

Demikian pula seorang setter terlihat, secara kontraktual, seperti itu hanya mengubah keadaan suatu objek. Mungkin melakukannya dengan cara yang berbelit-belit - menulis sebuah UPDATEke database, atau meneruskan parameter ke beberapa objek internal. Tidak apa-apa, tetapi melakukan sesuatu yang tidak terkait dengan pengaturan negara akan mengejutkan.

Meyer (pencipta Eiffel) juga mengatakan hal-hal tentang validasi. Pada dasarnya, setiap kali sebuah objek diam itu harus dalam keadaan valid. Jadi tepat setelah konstruktor selesai, sebelum dan sesudah (tetapi tidak harus selama) setiap panggilan metode eksternal, keadaan objek harus konsisten.

Sangat menarik untuk dicatat bahwa dalam bahasa itu, sintaks untuk memanggil prosedur dan untuk membaca atribut yang terbuka keduanya terlihat sama. Dengan kata lain, penelepon tidak dapat mengetahui apakah mereka menggunakan beberapa metode atau bekerja secara langsung dengan variabel instan. Ini hanya dalam bahasa yang tidak menyembunyikan detail implementasi ini di mana pertanyaan bahkan muncul — jika penelepon tidak bisa mengatakannya, Anda bisa beralih di antara ivar publik dan pengakses tanpa membocorkannya ke dalam kode klien.


sumber
1

Hal lain yang dapat diterima untuk dilakukan adalah kloning. Dalam beberapa kasus, Anda perlu memastikan, bahwa setelah seseorang memberikan kelas Anda misalnya. daftar sesuatu, ia tidak dapat mengubahnya di dalam kelas Anda (atau mengubah objek dalam daftar itu). Oleh karena itu Anda membuat salinan parameter dalam setter dan mengembalikan salinan dalam pada pengambil. (Menggunakan tipe yang tidak dapat diubah sebagai parameter adalah opsi tambahan, tetapi di atas dengan asumsi itu tidak mungkin) Tapi jangan mengkloning pada pengakses jika tidak diperlukan. Sangat mudah untuk berpikir (tetapi tidak tepat) tentang barang / getter dan setter sebagai operasi biaya konstan, jadi ini ranjau darat kinerja menunggu pengguna api.

pengguna470365
sumber
1

Tidak selalu ada pemetaan 1-1 antara pengakses properti dan ivars yang menyimpan data.

Misalnya, pandangan kelas mungkin memberikan centerproperti meskipun tidak ada Ivar yang menyimpan pusat pandangan; pengaturan centermenyebabkan perubahan pada ivars lain, seperti originatau transformatau apa pun, tetapi klien kelas tidak tahu atau peduli bagaimana center disimpan asalkan itu berfungsi dengan benar. Apa yang seharusnya tidak terjadi, bagaimanapun, adalah bahwa pengaturan centermenyebabkan sesuatu terjadi di luar apa pun yang diperlukan untuk menyelamatkan nilai baru, namun itu dilakukan.

Caleb
sumber
0

Bagian terbaik tentang setter dan getter adalah mereka membuatnya mudah untuk mengubah aturan API tanpa mengubah API. Jika Anda mendeteksi bug, kemungkinan besar Anda dapat memperbaiki bug di perpustakaan dan tidak setiap konsumen memperbarui basis kodenya.

AlexanderBrevig
sumber
-4

Saya cenderung percaya bahwa setter dan getter itu jahat dan hanya boleh digunakan di kelas yang dikelola oleh kerangka / wadah. Desain kelas yang tepat seharusnya tidak menghasilkan getter dan setter.

Sunting: artikel yang ditulis dengan baik tentang hal ini .

Sunting2: bidang publik adalah omong kosong dalam pendekatan OOP; dengan mengatakan bahwa getter dan setter adalah jahat, saya tidak bermaksud bahwa mereka harus digantikan oleh bidang publik.

m3th0dman
sumber
1
Saya pikir Anda melewatkan pokok artikel, yang menyarankan menggunakan getter / setter hemat dan untuk menghindari mengekspos data kelas kecuali diperlukan. IMHO ini adalah prinsip desain yang masuk akal yang harus diterapkan dengan sengaja oleh pengembang. Dalam hal membuat properti di kelas, Anda bisa saja mengekspos variabel, tetapi ini membuatnya lebih sulit untuk memberikan validasi ketika variabel diatur, atau untuk menarik nilai dari sumber alternatif ketika "didapat", pada dasarnya melanggar enkapsulasi, dan berpotensi mengunci Anda ke dalam desain antarmuka yang sulit dipertahankan.
S.Robins
@ S.Robins Menurut pendapat saya, getter dan setter publik salah; bidang publik lebih salah (jika itu sebanding).
m3th0dman
@methodman Ya, saya setuju bahwa bidang publik salah, namun properti publik dapat bermanfaat. Properti itu dapat digunakan untuk menyediakan tempat untuk validasi atau peristiwa yang berkaitan dengan pengaturan atau pengembalian data, tergantung pada persyaratan spesifik pada saat itu. Getters dan setters sendiri tidak salah per-se. Di sisi lain, bagaimana dan kapan mereka digunakan atau disalahgunakan dapat dianggap buruk dalam hal desain dan pemeliharaan tergantung pada keadaan. :)
S.Robins
@ S.Robins Pikirkan tentang dasar-dasar OOP dan pemodelan; objek seharusnya menyalin entitas kehidupan nyata. Adakah entitas kehidupan nyata yang memiliki jenis operasi / properti seperti getter / setter?
m3th0dman
Untuk menghindari terlalu banyak komentar di sini, saya akan membawa percakapan ini ke obrolan ini dan menjawab komentar Anda di sana.
S.Robins