Accessor dan modifier (alias setter dan getter) berguna untuk tiga alasan utama:
- Mereka membatasi akses ke variabel.
- Misalnya, variabel dapat diakses, tetapi tidak dimodifikasi.
- Mereka memvalidasi parameter.
- Mereka dapat menyebabkan beberapa efek samping.
Universitas, kursus online, tutorial, artikel blog, dan contoh kode di web semuanya menekankan tentang pentingnya pengakses dan pengubah, mereka hampir merasa seperti "harus memiliki" untuk kode saat ini. Jadi orang dapat menemukannya bahkan ketika mereka tidak memberikan nilai tambahan, seperti kode di bawah ini.
public class Cat {
private int age;
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
Yang telah dikatakan, sangat umum untuk menemukan pengubah yang lebih berguna, yang sebenarnya memvalidasi parameter dan melemparkan pengecualian atau mengembalikan boolean jika input yang tidak valid telah disediakan, sesuatu seperti ini:
/**
* Sets the age for the current cat
* @param age an integer with the valid values between 0 and 25
* @return true if value has been assigned and false if the parameter is invalid
*/
public boolean setAge(int age) {
//Validate your parameters, valid age for a cat is between 0 and 25 years
if(age > 0 && age < 25) {
this.age = age;
return true;
}
return false;
}
Tetapi bahkan kemudian, saya hampir tidak pernah melihat pemodifikasi dipanggil dari sebuah konstruktor, jadi contoh paling umum dari kelas sederhana yang saya hadapi adalah:
public class Cat {
private int age;
public Cat(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
/**
* Sets the age for the current cat
* @param age an integer with the valid values between 0 and 25
* @return true if value has been assigned and false if the parameter is invalid
*/
public boolean setAge(int age) {
//Validate your parameters, valid age for a cat is between 0 and 25 years
if(age > 0 && age < 25) {
this.age = age;
return true;
}
return false;
}
}
Tetapi orang akan berpikir bahwa pendekatan kedua ini jauh lebih aman:
public class Cat {
private int age;
public Cat(int age) {
//Use the modifier instead of assigning the value directly.
setAge(age);
}
public int getAge() {
return this.age;
}
/**
* Sets the age for the current cat
* @param age an integer with the valid values between 0 and 25
* @return true if value has been assigned and false if the parameter is invalid
*/
public boolean setAge(int age) {
//Validate your parameters, valid age for a cat is between 0 and 25 years
if(age > 0 && age < 25) {
this.age = age;
return true;
}
return false;
}
}
Apakah Anda melihat pola yang sama dalam pengalaman Anda atau hanya karena saya tidak beruntung? Dan jika Anda melakukannya, lalu apa yang menurut Anda menyebabkan itu? Apakah ada kerugian yang jelas untuk menggunakan pengubah dari konstruktor atau mereka hanya dianggap lebih aman? Apakah ini sesuatu yang lain?
sumber
Jawaban:
Alasan filosofis yang sangat umum
Biasanya, kami meminta konstruktor memberikan (sebagai kondisi pasca) beberapa jaminan tentang keadaan objek yang dibangun.
Biasanya, kami juga berharap bahwa metode instan dapat mengasumsikan (sebagai prasyarat) bahwa jaminan ini sudah berlaku ketika mereka dipanggil, dan mereka hanya harus memastikan untuk tidak melanggarnya.
Memanggil metode instance dari dalam konstruktor berarti beberapa atau semua jaminan itu mungkin belum dibuat, yang membuat sulit untuk berpikir tentang apakah pra-kondisi metode instance terpenuhi. Bahkan jika Anda melakukannya dengan benar, itu bisa sangat rapuh di wajah, misalnya. memesan kembali panggilan metode instan atau operasi lainnya.
Bahasa juga bervariasi dalam cara mereka menyelesaikan panggilan ke metode contoh yang diwarisi dari kelas dasar / diganti oleh sub-kelas, sementara konstruktor masih berjalan. Ini menambah lapisan kompleksitas lainnya.
Contoh spesifik
Contoh Anda sendiri tentang bagaimana menurut Anda seharusnya terlihat salah:
ini tidak memeriksa nilai pengembalian dari
setAge
. Rupanya memanggil setter bukanlah jaminan kebenaran.Kesalahan yang sangat mudah seperti tergantung pada urutan inisialisasi, seperti:
di mana penebangan sementara saya merusak segalanya. Aduh!
Ada juga bahasa seperti C ++ di mana memanggil setter dari konstruktor berarti inisialisasi default yang terbuang (yang untuk beberapa variabel anggota setidaknya layak dihindari)
Usulan sederhana
Memang benar bahwa sebagian besar kode tidak ditulis seperti ini, tetapi jika Anda ingin menjaga konstruktor Anda tetap bersih dan dapat diprediksi, dan masih menggunakan kembali logika pra dan pasca kondisi Anda, solusi yang lebih baik adalah:
atau bahkan lebih baik, jika mungkin: menyandikan kendala dalam tipe properti, dan minta nilai validasinya sendiri pada penugasan:
Dan akhirnya, untuk kelengkapan lengkap, pembungkus validasi diri dalam C ++. Perhatikan bahwa meskipun masih melakukan validasi yang membosankan, karena kelas ini tidak melakukan hal lain , ini relatif mudah untuk diperiksa
OK, itu tidak benar-benar selesai, saya telah meninggalkan berbagai salinan dan memindahkan konstruktor dan tugas.
sumber
Ketika suatu objek sedang dibangun, itu secara definisi tidak sepenuhnya terbentuk. Pertimbangkan bagaimana setter yang Anda berikan:
Bagaimana jika bagian dari validasi
setAge()
termasuk cek terhadapage
properti untuk memastikan bahwa objek hanya dapat bertambah umur? Kondisi itu mungkin terlihat seperti:Itu sepertinya perubahan yang sangat tidak bersalah, karena kucing hanya dapat bergerak melalui waktu dalam satu arah. Satu-satunya masalah adalah bahwa Anda tidak dapat menggunakannya untuk menetapkan nilai awal
this.age
karena mengasumsikanthis.age
sudah memiliki nilai yang valid.Menghindari setter dalam konstruktor membuatnya jelas bahwa satu satunya hal yang dilakukan konstruktor adalah mengatur variabel instan dan melewatkan perilaku lain yang terjadi pada setter.
sumber
Beberapa jawaban yang baik sudah ada di sini, tetapi sejauh ini belum ada yang menyadari bahwa Anda bertanya di sini dua hal dalam satu, yang menyebabkan kebingungan. IMHO pos Anda paling baik dilihat sebagai dua pertanyaan terpisah :
jika ada validasi kendala dalam setter, bukankah harus juga di konstruktor kelas untuk memastikan itu tidak dapat dilewati?
mengapa tidak memanggil setter langsung untuk tujuan ini dari konstruktor?
Jawaban untuk pertanyaan pertama jelas "ya". Konstruktor, ketika tidak membuang pengecualian, harus meninggalkan objek dalam keadaan valid, dan jika nilai-nilai tertentu dilarang untuk atribut tertentu, maka sama sekali tidak masuk akal untuk membiarkan ctor mengelak dari ini.
Namun, jawaban untuk pertanyaan kedua biasanya "tidak", selama Anda tidak mengambil tindakan untuk menghindari mengesampingkan setter di kelas turunan. Oleh karena itu, alternatif yang lebih baik adalah menerapkan validasi kendala dalam metode pribadi yang dapat digunakan kembali dari setter maupun dari konstruktor. Saya tidak akan mengulangi contoh @Useless di sini, yang menunjukkan persis desain ini.
sumber
Berikut adalah sedikit kode Java konyol yang menunjukkan jenis masalah yang dapat Anda hadapi dengan menggunakan metode non-final dalam konstruktor Anda:
Ketika saya menjalankan ini, saya mendapatkan NPE di konstruktor NextProblem. Ini adalah contoh sepele tentu saja tetapi hal-hal dapat menjadi rumit dengan cepat jika Anda memiliki beberapa tingkat warisan.
Saya pikir alasan yang lebih besar ini tidak menjadi umum adalah karena itu membuat kode sedikit lebih sulit untuk dipahami. Secara pribadi, saya hampir tidak pernah memiliki metode setter dan variabel anggota saya hampir selalu final (pun intended). Karena itu, nilai harus ditetapkan dalam konstruktor. Jika Anda menggunakan objek yang tidak dapat diubah (dan ada banyak alasan bagus untuk melakukannya) pertanyaannya bisa diperdebatkan.
Bagaimanapun, itu adalah tujuan yang baik untuk menggunakan kembali validasi atau logika lain dan Anda dapat memasukkannya ke metode statis dan memintanya dari konstruktor dan setter.
sumber
name
bidang).this
-referensi ke beberapa metode lain, karena Anda belum dapat memastikan sepenuhnya diinisialisasi.Tergantung!
Jika Anda melakukan validasi sederhana atau mengeluarkan efek samping nakal di setter yang perlu dipukul setiap kali properti disetel: gunakan setter. Alternatifnya adalah dengan mengekstrak logika setter dari setter dan memanggil metode baru diikuti oleh set aktual dari setter dan konstruktor - yang secara efektif sama dengan menggunakan setter! (Kecuali sekarang Anda mempertahankan, setter dua baris yang diakui, kecil di dua tempat.)
Namun , jika Anda perlu melakukan operasi kompleks untuk mengatur objek sebelum setter tertentu (atau dua!) Berhasil dieksekusi, jangan gunakan setter.
Dan dalam kedua kasus, ada kasus uji . Terlepas dari rute mana yang Anda tuju, jika Anda memiliki unit test, Anda akan memiliki peluang bagus untuk mengekspos perangkap yang terkait dengan salah satu opsi.
Saya juga akan menambahkan, jika ingatan saya dari masa lalu itu bahkan jauh akurat, ini adalah semacam alasan saya diajarkan di perguruan tinggi juga. Dan, itu sama sekali tidak sejalan dengan proyek-proyek sukses yang saya punya hak istimewa untuk dikerjakan.
Jika saya harus menebak, saya melewatkan memo "kode bersih" di beberapa titik yang mengidentifikasi beberapa bug khas yang dapat timbul dari menggunakan setter dalam konstruktor. Tapi, untuk bagian saya, saya belum menjadi korban bug seperti itu ...
Jadi, saya berpendapat bahwa keputusan khusus ini tidak perlu menyapu dan dogmatis.
sumber