Menggunakan final publik daripada getter pribadi

51

Saya melihat POJO paling abadi ditulis seperti ini:

public class MyObject {
    private final String foo;
    private final int bar;

    public MyObject(String foo, int bar) {
        this.foo = foo;
        this.bar = bar;
    }

    public String getFoo() {
        return foo;
    }

    public int getBar() {
        return bar;
    }
}

Namun saya cenderung menulisnya seperti ini:

public class MyObject {
    public final String foo;
    public final int bar;

    public MyObject(String foo, int bar) {
        this.foo = foo;
        this.bar = bar;
    }
}

Perhatikan referensi bersifat final, sehingga Object tetap tidak berubah. Ini memungkinkan saya menulis lebih sedikit kode dan memungkinkan lebih pendek (dengan 5 karakter: getdan ()) akses.

Satu-satunya kelemahan yang bisa saya lihat adalah jika Anda ingin mengubah implementasi getFoo()jalan untuk melakukan sesuatu yang gila, Anda tidak bisa. Tetapi secara realistis, ini tidak pernah terjadi karena Objek tidak dapat diubah; Anda dapat memverifikasi selama instantiasi, membuat salinan defensif yang tidak berubah selama instantiasi (lihat ImmutableListcontoh Guava ), dan menyiapkan fooatau barobjek untuk getpanggilan.

Apakah ada kerugian yang saya lewatkan?

SUNTING

Saya kira kerugian lain yang saya lewatkan adalah serialisasi perpustakaan menggunakan refleksi atas metode yang dimulai dengan getatau is, tapi itu praktik yang sangat mengerikan ...

Cory Kendall
sumber
Apa apaan? finaltidak membuat variabel objek tidak berubah. Saya biasanya menggunakan desain di mana saya mendefinisikan finalbidang sebelum membuat panggilan balik sehingga panggilan balik dapat mengakses bidang itu. Itu bisa, tentu saja memanggil semua metode termasuk setXmetode apa pun .
Tomáš Zato
@ TomášZato Tidak ada metode setX pada String, intatau MyObject. Di versi pertama, finalhanya untuk memastikan bahwa metode selain konstruktor di dalam kelas tidak mencoba bar = 7;, misalnya. Dalam versi kedua, finaldiperlukan untuk mencegah konsumen dari melakukan: MyObject x = new MyObject("hi", 5); x.bar = 7;.
Cory Kendall
1
Yah, " Perhatikan bahwa rujukannya final, jadi Objectmasih tetap. " Menyesatkan - dengan cara itu tampaknya Anda berpikir ada yang final Objecttidak berubah, padahal tidak. Maaf atas kesalahpahaman ini.
Tomáš Zato
3
Jika nilai yang mendasarinya bisa berubah, jalan pribadi + accessors tidak akan mencegahnya - myObj.getFoo().setFrob(...).
Beni Cherniavsky-Paskin

Jawaban:

38

Empat kelemahan yang bisa saya pikirkan:

  1. Jika Anda ingin memiliki bentuk read-only dan bisa berubah dari entitas yang sama, pola umum adalah memiliki Entitas kelas yang tidak berubah yang memperlihatkan hanya pengakses dengan variabel anggota yang dilindungi, kemudian buat MutableEntity yang meluas dan menambahkan setter. Versi Anda mencegahnya.
  2. Penggunaan getter dan setter mematuhi konvensi JavaBeans. Jika Anda ingin menggunakan kelas Anda sebagai kacang dalam teknologi berbasis properti, seperti JSTL atau EL, Anda perlu memaparkan getter publik.
  3. Jika Anda ingin mengubah implementasi untuk mendapatkan nilai-nilai atau mencarinya di database, Anda harus memperbaiki kode klien. Pendekatan accessor / mutator memungkinkan Anda untuk hanya mengubah implementasi.
  4. Terkejut - ketika saya melihat variabel instance publik, saya segera mencari siapa yang mungkin memutasinya dan khawatir bahwa saya membuka kotak pandora karena enkapsulasi hilang. http://en.wikipedia.org/wiki/Principle_of_least_astonishment

Yang mengatakan, versi Anda pasti lebih ringkas. Jika ini adalah kelas khusus yang hanya digunakan dalam paket tertentu (mungkin ruang lingkup paket adalah ide yang bagus di sini), maka saya dapat mempertimbangkan ini untuk sekali saja. Tapi saya tidak akan mengekspos API utama seperti ini.

Brandon
sumber
3
Jawaban bagus; Saya memiliki ketidaksetujuan dengan beberapa dari mereka, tetapi saya tidak yakin situs ini adalah forum terbaik untuk diskusi. Singkatnya, 1) Saya dapat mematahkan yang lain dengan menggunakan bentuk yang bisa berubah di mana mereka menganggap itu tidak dapat diubah (mungkin saya harus menambahkan final ke kelas dalam contoh saya), 2) Saya setuju, 3) Saya berpendapat itu bukan lagi POJO dalam hal itu kasus, 4) Saya setuju untuk saat ini, tapi semoga diskusi seperti ini dapat mengubah apa yang paling mencengangkan :).
Cory Kendall
2
5) Anda tidak dapat dengan aman mengekspos kelas / tipe yang bisa berubah secara inheren - seperti array. Cara aman adalah mengembalikan salinan dari pengambil setiap kali.
Clockwork-Muse
@ Clockwork-Muse Anda bisa, jika Anda menganggap orang bukan idiot. Saat mengakses someObj.thisList.add (), jelas Anda memodifikasi beberapa objek lainnya. Dengan someObj.getThisList (). Add () tidak diketahui. Anda perlu menanyakan dokumentasi atau mencari sumbernya, tetapi dari metode deklarasi sendiri tidak mungkin untuk mengatakannya.
kritzikratzi
2
@kritzikratzi - Masalahnya adalah Anda tidak dapat menjamin bahwa kode Anda tidak akan menjadi idiot. Pada titik tertentu itu akan (yang bahkan mungkin berakhir menjadi diri sendiri!), Dan kejutan itu bisa menjadi masalah besar.
Clockwork-Muse
1
Nah, membiarkan semua jenis yang dapat berubah mewarisi dari jenis yang tidak dapat diubah secara inheren salah. Ingat bahwa tipe yang tidak berubah memberikan jaminan "Saya tidak berubah. Pernah.".
Deduplicator
26

Singkirkan getter / setter juga, dan Anda baik-baik saja!

Ini adalah topik yang sangat kontroversial di kalangan programmer Java.

Bagaimanapun, ada dua situtation di mana saya menggunakan variabel publik sebagai gantinya (!) Dari getter / setter:

  1. public final Bagi saya ini menandakan "Saya tidak berubah" jauh lebih baik daripada sekadar rajin. Sebagian besar IDE akan menunjukkan pengubah akhir dengan 'F' selama pelengkapan otomatis. Berbeda dengan getter / setter, di mana Anda harus mencari tidak adanya setXXX.
  2. publik non-final Saya suka ini untuk kelas data. Saya hanya mengekspos semua bidang secara publik. Tidak ada getter, setter, konstruktor. Tidak apa-apa Kurang dari satu pojo. Bagi saya ini segera memberi sinyal "lihat, saya bodoh. Saya memegang data, itu saja. Adalah tugas ANDA untuk menempatkan data yang tepat di dalam diri saya". Gson / JAXB / dll. menangani kelas-kelas ini dengan baik. Mereka adalah kebahagiaan untuk menulis. Tidak ada keraguan tentang tujuan atau kemampuan mereka. Dan yang paling penting: Anda tahu tidak ada efek samping ketika Anda mengubah variabel. IMHO ini menghasilkan model data yang sangat ringkas dengan sedikit ambiguitas, sedangkan getter dan setter memiliki masalah besar ini di mana terkadang keajaiban terjadi di dalamnya.
kritzikratzi
sumber
5
Senang melihat kewarasan sesekali :)
Navin
2
Hingga saatnya tiba di mana model Anda berkembang dan salah satu bidang lama sekarang dapat diturunkan dari yang lain. Karena Anda ingin mempertahankan kompatibilitas ke belakang, bidang lama harus tetap tetapi alih-alih hanya mengubah kode pengambil dan penyetel, Anda harus mengubah di mana pun tempat bidang lama ini digunakan untuk memastikan bahwa itu mengikuti representasi model baru. Mudah ditulis .. ya tentu saja ... mimpi buruk untuk dipertahankan dalam jangka panjang ... Itulah sebabnya kita memiliki pengambil / penyetel atau lebih baik lagi di aksesor properti C #.
Newtopian
9

Dalam kata-kata awam:

  • Anda melanggar enkapsulasi untuk menyimpan beberapa baris kode. Itu mengalahkan tujuan OOD.
  • Kode klien akan sulit digabungkan dengan nama anggota kelas Anda. Kopling buruk. Seluruh tujuan OOD mencegah kopling.
  • Anda juga sangat yakin kelas Anda tidak akan pernah bisa berubah. Banyak hal berubah. Perubahan adalah satu-satunya hal yang konstan.
Tulains Córdova
sumber
6
Bagaimana ini merupakan pelanggaran enkapsulasi? Kedua versi ini sama-sama terpapar dengan dunia luar satu sama lain (dengan akses hanya baca). Anda benar tentang kopling keras dan tidak fleksibelnya perubahan, namun, saya tidak akan membantahnya.
KChaloux
4
@KChaloux Anda mengacaukan "pelanggaran enkapsulasi" dengan "kode gagal dikompilasi dan harus membuang waktu untuk memperbaikinya", salah satunya terjadi terlebih dahulu. Yang kedua terjadi kemudian. Untuk membuatnya jelas: enkapsulasi sudah dilanggar di masa sekarang. Jeroan kelas Anda tidak lagi dienkapsulasi pada saat ini. Tetapi kode klien akan rusak di masa depan jika kelas berubah di masa depan, karena enkapsulasi rusak di masa lalu. Ada dua "break" berbeda, enkapsulasi hari ini, kode klien besok, karena kurangnya enkapsulasi.
Tulains Córdova
8
Secara teknis, ketika Anda menggunakan getter / etc. kode klien akan sulit digabungkan ke nama metode. Lihatlah berapa banyak perpustakaan yang harus menyertakan metode versi lama dengan tag / anotasi "usang" karena kode lama masih bergantung pada mereka (dan beberapa orang mungkin masih menggunakannya karena mereka merasa lebih mudah untuk digunakan, tetapi Anda tidak seharusnya melakukan itu).
JAB
5
Pragmatis: Seberapa sering programmer benar-benar memperbaiki nama bidang pribadi tanpa mengganti nama getter / setter publik? Dari apa yang bisa saya katakan, ada konvensi di seluruh komunitas yang ada untuk menamai mereka sama, bahkan jika konvensi itu mungkin hanya hasil dari alat IDE yang menghasilkan getter dan setter dari nama bidang. Teoritis: Dari perspektif pemodelan berorientasi objek, bagaimanapun, publik apa pun adalah bagian dari kontrak publik, dan bukan detail implementasi internal. Jadi jika seseorang memutuskan untuk membuat bidang publik terakhir, bukankah mereka mengatakan ini adalah kontrak yang mereka janjikan untuk dipenuhi?
Thomas Jung
3
@ user949300 Katakan yang mana supaya saya bisa belajar.
Tulains Córdova
6

Salah satu kelemahan yang mungkin saya lihat begitu saja adalah Anda terikat pada representasi internal data di kelas. Ini mungkin bukan masalah besar, tetapi jika Anda menggunakan setter, dan Anda memutuskan bahwa foo dan bar akan dikembalikan dari beberapa kelas lain yang ditentukan di tempat lain, kelas yang mengkonsumsi MyObject tidak akan diminta untuk berubah. Anda hanya perlu menyentuh MyObject. Namun, jika Anda menggunakan nilai telanjang, maka Anda harus menyentuh di mana saja MyObject yang saya gunakan.

ipaul
sumber