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.