Mengapa kaedah getter dan setter itu jahat

Saya tidak berniat untuk memulakan siri "is jahat", tetapi beberapa pembaca meminta saya untuk menjelaskan mengapa saya menyebut bahawa anda harus mengelakkan kaedah mendapatkan / menetapkan dalam lajur bulan lalu, "Mengapa perluasan adalah Jahat."

Walaupun metode getter / setter adalah hal yang biasa di Java, mereka tidak berorientasikan objek (OO). Sebenarnya, ia boleh merosakkan pemeliharaan kod anda. Lebih-lebih lagi, kehadiran banyak kaedah getter dan setter adalah bendera merah bahawa program ini tidak semestinya dirancang dengan baik dari perspektif OO.

Artikel ini menerangkan mengapa anda tidak boleh menggunakan getter dan setter (dan kapan anda boleh menggunakannya) dan mencadangkan metodologi reka bentuk yang akan membantu anda keluar dari mentaliti getter / setter.

Mengenai sifat reka bentuk

Sebelum saya melancarkan ruangan lain yang berkaitan dengan reka bentuk (dengan tajuk yang provokatif, tidak kurang), saya ingin menjelaskan beberapa perkara.

Saya terpesona oleh beberapa komen pembaca yang dihasilkan dari lajur bulan lalu, "Why extends Is Evil" (lihat Talkback di halaman terakhir artikel). Sebilangan orang percaya bahawa saya berpendapat bahawa orientasi objek buruk hanya kerana extendsmempunyai masalah, seolah-olah kedua konsep itu setara. Itu tentu bukan yang saya fikirkan , jadi izinkan saya menjelaskan beberapa masalah meta.

Lajur ini dan artikel bulan lalu adalah mengenai reka bentuk. Reka bentuk, secara semula jadi, adalah rangkaian pertukaran. Setiap pilihan mempunyai sisi baik dan buruk, dan anda membuat pilihan dalam konteks kriteria keseluruhan yang ditentukan oleh keperluan. Baik dan buruk tidak mutlak, bagaimanapun. Keputusan yang baik dalam satu konteks mungkin buruk dalam konteks yang lain.

Sekiranya anda tidak memahami kedua-dua sisi masalah, anda tidak boleh membuat pilihan yang bijak; sebenarnya, jika anda tidak memahami semua kesan tindakan anda, anda sama sekali tidak merancang. Anda tersandung dalam kegelapan. Bukan kebetulan bahawa setiap bab dalam buku Corak Reka Bentuk Gang of Four merangkumi bahagian "Konsekuensi" yang menerangkan kapan dan mengapa menggunakan corak tidak sesuai.

Menyatakan bahawa beberapa ciri bahasa atau ungkapan pengaturcaraan umum (seperti aksesori) mempunyai masalah tidak sama dengan mengatakan bahawa anda tidak boleh menggunakannya dalam keadaan apa pun. Dan hanya kerana ciri atau simpulan bahasa yang biasa digunakan tidak bermaksud anda harus menggunakannya. Pengaturcara yang tidak berpengetahuan menulis banyak program dan hanya digunakan oleh Sun Microsystems atau Microsoft tidak meningkatkan kemampuan pengaturcaraan atau reka bentuk seseorang secara ajaib. Pakej Java mengandungi banyak kod hebat. Tetapi ada juga bahagian dari kod itu yang saya pasti penulis malu untuk mengakui bahawa mereka menulis.

Dengan cara yang sama, pemasaran atau insentif politik sering mendorong idiom reka bentuk. Kadang kala pengaturcara membuat keputusan yang tidak baik, tetapi syarikat ingin mempromosikan teknologi yang boleh dilakukan, oleh itu mereka menekankan bahawa cara anda melakukannya adalah kurang ideal. Mereka membuat yang terbaik dari keadaan buruk. Akibatnya, anda bertindak secara tidak bertanggungjawab semasa anda mengamalkan amalan pengaturcaraan hanya kerana "itulah cara anda seharusnya melakukan sesuatu." Banyak projek Enterprise JavaBeans (EJB) yang gagal membuktikan prinsip ini. Teknologi berasaskan EJB adalah teknologi hebat apabila digunakan dengan tepat, tetapi secara harfiah dapat menjatuhkan syarikat jika digunakan dengan tidak tepat.

Maksud saya ialah anda tidak boleh memprogram secara membabi buta Anda mesti memahami malapetaka suatu ciri atau idiom yang boleh menimbulkan. Dengan berbuat demikian, anda berada dalam kedudukan yang lebih baik untuk memutuskan sama ada anda harus menggunakan ciri atau simpulan bahasa itu. Pilihan anda harus tepat dan pragmatik. Tujuan artikel ini adalah untuk membantu anda mendekati program anda dengan mata terbuka.

Pengambilan data

Prinsip asas sistem OO adalah bahawa objek tidak boleh mendedahkan detail pelaksanaannya. Dengan cara ini, anda dapat mengubah pelaksanaan tanpa mengubah kod yang menggunakan objek. Oleh itu, dalam sistem OO, anda harus mengelakkan fungsi getter dan setter kerana kebanyakannya memberi akses kepada perincian pelaksanaan.

Untuk mengetahui sebabnya, pertimbangkan bahawa mungkin terdapat 1.000 panggilan ke getX()kaedah dalam program anda, dan setiap panggilan menganggap bahawa nilai pengembalian adalah jenis tertentu. Anda mungkin menyimpan getX()nilai pulangan dalam pemboleh ubah tempatan, misalnya, dan jenis pemboleh ubah itu mesti sepadan dengan jenis nilai kembali. Sekiranya anda perlu mengubah cara objek dilaksanakan sedemikian rupa sehingga jenis X berubah, anda menghadapi masalah besar.

Sekiranya X adalah int, tetapi sekarang mesti menjadi long, anda akan mendapat 1,000 kesalahan kompilasi. Sekiranya anda tidak betul menyelesaikan masalah dengan memberikan nilai kembali int, kod akan disusun dengan bersih, tetapi tidak akan berfungsi. (Nilai pengembalian mungkin dipotong.) Anda mesti mengubah kod yang mengelilingi setiap 1,000 panggilan tersebut untuk mengimbangi perubahan tersebut. Saya pasti tidak mahu melakukan banyak kerja.

Salah satu prinsip asas sistem OO adalah pengambilan data . Anda harus menyembunyikan sepenuhnya cara objek melaksanakan pengendali mesej dari program yang lain. Itulah satu sebab mengapa semua pemboleh ubah contoh anda (medan tidak konstanta kelas) mestilah private.

Sekiranya anda membuat pemboleh ubah instance public, maka anda tidak dapat mengubah medan kerana kelas berkembang dari masa ke masa kerana anda akan memecahkan kod luaran yang menggunakan medan. Anda tidak mahu mencari 1,000 penggunaan kelas hanya kerana anda menukar kelas itu.

Prinsip penyembunyian pelaksanaan ini membawa kepada ujian asid yang baik terhadap kualiti sistem OO: Bolehkah anda membuat perubahan besar pada definisi kelas — bahkan membuang keseluruhannya dan menggantinya dengan pelaksanaan yang sama sekali berbeza — tanpa mempengaruhi mana-mana kod yang menggunakannya objek kelas? Modularisasi semacam ini adalah premis utama orientasi objek dan menjadikan penyelenggaraan menjadi lebih mudah. Tanpa penyembunyian pelaksanaan, tidak ada gunanya menggunakan ciri OO lain.

Kaedah getter dan setter (juga dikenali sebagai aksesor) berbahaya dengan alasan yang sama bahawa publicbidang berbahaya: Mereka menyediakan akses luaran ke perincian pelaksanaan. Bagaimana jika anda perlu menukar jenis medan yang diakses? Anda juga harus menukar jenis pemulangan aksesor. Anda menggunakan nilai pulangan ini di banyak tempat, jadi anda juga mesti mengubah semua kod tersebut. Saya mahu menghadkan kesan perubahan kepada definisi kelas tunggal. Saya tidak mahu mereka masuk ke dalam keseluruhan program.

Oleh kerana aksesor melanggar prinsip enkapsulasi, anda boleh secara munasabah berpendapat bahawa sistem yang menggunakan aksesori dengan banyak atau tidak tepat tidak berorientasikan objek. Sekiranya anda melalui proses reka bentuk, berbanding pengekodan, anda tidak akan mendapat aksesori dalam program anda. Prosesnya penting. Saya mempunyai lebih banyak perkara mengenai isu ini di akhir artikel.

Kekurangan kaedah getter / setter tidak bermaksud bahawa beberapa data tidak mengalir melalui sistem. Walaupun begitu, lebih baik meminimumkan pergerakan data sebanyak mungkin. Pengalaman saya adalah bahawa pemeliharaan berkadar terbalik dengan jumlah data yang bergerak di antara objek. Walaupun anda mungkin belum melihatnya, anda sebenarnya dapat menghilangkan sebahagian besar pergerakan data ini.

Dengan merancang dengan teliti dan memfokuskan pada apa yang mesti anda lakukan dan bukannya bagaimana anda melakukannya, anda menghilangkan sebahagian besar kaedah getter / setter dalam program anda. Jangan meminta maklumat yang anda perlukan untuk melakukan kerja; minta objek yang mempunyai maklumat untuk melakukan kerja untuk anda.Sebilangan besar aksesor mencari jalan ke dalam kod kerana pereka tidak memikirkan model dinamik: objek runtime dan mesej yang mereka kirimkan satu sama lain untuk melakukan kerja. Mereka memulakan (tidak betul) dengan merancang hierarki kelas dan kemudian cuba mengarahkan kelas tersebut ke dalam model dinamik. Pendekatan ini tidak pernah berjaya. Untuk membina model statik, anda perlu mengetahui hubungan antara kelas, dan hubungan ini sesuai dengan aliran mesej. Perkaitan wujud antara dua kelas hanya apabila objek dari satu kelas menghantar mesej ke objek yang lain. Tujuan utama model statik adalah untuk menangkap maklumat hubungan ini semasa anda membuat model secara dinamik.

Tanpa model dinamik yang jelas, anda hanya dapat meneka bagaimana anda akan menggunakan objek kelas. Oleh itu, kaedah aksesor sering berlaku dalam model kerana anda mesti menyediakan akses sebanyak mungkin kerana anda tidak dapat meramalkan sama ada anda memerlukannya atau tidak. Strategi reka bentuk dengan meneka jenis ini tidak berkesan. Anda membuang masa untuk menulis kaedah yang tidak berguna (atau menambah keupayaan yang tidak perlu ke kelas).

Aksesori juga mempunyai reka bentuk dengan kebiasaan. Apabila pengaturcara prosedural mengadopsi Java, mereka cenderung memulai dengan membangun kod yang sudah biasa. Bahasa prosedur tidak mempunyai kelas, tetapi mereka mempunyai C struct(fikir: kelas tanpa kaedah). Oleh itu, wajar untuk meniru structdengan membina definisi kelas dengan hampir tidak ada kaedah dan hanya publicbidang. Pengaturcara prosedural ini membaca di suatu tempat bahawa bidang harus private, bagaimanapun, sehingga mereka membuat bidang privatedan menyediakan publickaedah aksesor. Tetapi mereka hanya merumitkan akses orang ramai. Mereka pasti tidak membuat sistem berorientasikan objek.

Lukiskan dirimu

Satu ramalan enkapsulasi medan penuh adalah dalam pembinaan antara muka pengguna (UI). Sekiranya anda tidak dapat menggunakan aksesor, anda tidak boleh menggunakan kelas pembangun UI memanggil getAttribute()kaedah. Sebaliknya, kelas mempunyai elemen seperti drawYourself(...)kaedah.

A getIdentity()kaedah boleh juga kerja, sudah tentu, dengan syarat ia mengembalikan objek yang melaksanakan Identityantara muka. Antara muka ini mesti merangkumi kaedah drawYourself()(atau berikan-saya-a JComponent--yang-mewakili-identiti anda). Walaupun getIdentitydimulai dengan "get", itu bukan aksesor kerana tidak hanya mengembalikan bidang. Ia mengembalikan objek kompleks yang mempunyai tingkah laku yang wajar. Walaupun saya mempunyai Identityobjek, saya masih tidak tahu bagaimana identiti diwakili secara dalaman.

Sudah tentu, drawYourself()strategi bermaksud bahawa saya (terkesiap!) Memasukkan kod UI ke dalam logik perniagaan. Pertimbangkan apa yang berlaku apabila keperluan UI berubah. Katakan saya mahu mewakili atribut dengan cara yang sama sekali berbeza. Hari ini "identiti" adalah nama; esok itu nama dan nombor ID; sehari selepas itu nama, nombor ID, dan gambar. Saya menghadkan skop perubahan ini ke satu tempat dalam kod. Sekiranya saya mempunyai kelas beri-saya-a JComponent-yang-mewakili-identiti anda, maka saya telah mengasingkan cara identiti diwakili dari sistem yang lain.

Ingatlah bahawa saya sebenarnya tidak memasukkan kod UI ke dalam logik perniagaan. Saya telah menulis lapisan UI dalam bentuk AWT (Abstract Window Toolkit) atau Swing, yang merupakan kedua lapisan abstraksi. Kod UI sebenarnya ada dalam pelaksanaan AWT / Swing. Itulah inti dari lapisan abstraksi — untuk mengasingkan logik perniagaan anda dari mekanisme subsistem. Saya dapat dengan mudah dibawa ke persekitaran grafik yang lain tanpa mengubah kod, jadi satu-satunya masalah adalah kekacauan sedikit. Anda boleh menghilangkan kekacauan ini dengan mudah dengan memindahkan semua kod UI ke kelas dalam (atau dengan menggunakan corak reka bentuk Façade).

Kacang Java

Anda mungkin keberatan dengan mengatakan, "Tetapi bagaimana dengan JavaBeans?" Bagaimana dengan mereka? Anda pasti dapat membina JavaBeans tanpa pengawal dan pengatur. Semua BeanCustomizer, BeanInfodan BeanDescriptorkelas ada untuk tujuan ini. Pereka spesifikasi JavaBean melemparkan idiom getter / setter ke dalam gambar kerana mereka menyangka akan menjadi cara mudah untuk membuat kacang dengan cepat — sesuatu yang dapat anda lakukan semasa anda belajar bagaimana melakukannya dengan betul. Malangnya, tidak ada yang melakukan itu.

Aksesori dibuat semata-mata sebagai cara untuk menandai sifat tertentu supaya program pembangun UI atau yang setara dapat mengenalinya. Anda tidak seharusnya memanggil kaedah ini sendiri. Mereka ada untuk digunakan alat automatik. Alat ini menggunakan API introspeksi di Classkelas untuk mencari kaedah dan memperkirakan kewujudan sifat tertentu dari nama kaedah. Dalam praktiknya, ungkapan berdasarkan introspeksi ini tidak berjaya. Ini menjadikan kodnya terlalu rumit dan prosedur. Pengaturcara yang tidak memahami pengabstrakan data sebenarnya memanggil aksesorinya, dan akibatnya, kodnya kurang dapat dikendalikan. Atas sebab ini, ciri metadata akan dimasukkan ke dalam Java 1.5 (akan jatuh tempo pada pertengahan 2004). Oleh itu, bukannya:

harta int swasta; public int getProperty () {mengembalikan harta; } public void setProperty (nilai int} {harta = nilai;}

Anda akan dapat menggunakan sesuatu seperti:

harta peribadi @property int; 

Alat pembinaan UI atau yang setara akan menggunakan API introspeksi untuk mencari hartanah, daripada memeriksa nama kaedah dan menyimpulkan kewujudan harta dari sebuah nama. Oleh itu, tiada aksesori runtime merosakkan kod anda.

Bilakah aksesori baik-baik saja?

Pertama, seperti yang saya bincangkan sebelumnya, tidak mengapa kaedah mengembalikan objek dari segi antara muka yang dilaksanakan oleh objek kerana antara muka itu mengasingkan anda dari perubahan ke kelas pelaksana. Kaedah seperti ini (yang mengembalikan rujukan antara muka) sebenarnya bukan "pengambil" dalam arti kaedah yang hanya menyediakan akses ke medan. Sekiranya anda mengubah pelaksanaan dalaman penyedia, anda hanya mengubah definisi objek yang dikembalikan untuk menampung perubahan. Anda masih melindungi kod luaran yang menggunakan objek melalui antara muka.