Mengapa metode pengambil dan penyetel itu jahat

Saya tidak bermaksud untuk memulai serial "is evil", tetapi beberapa pembaca meminta saya untuk menjelaskan mengapa saya menyebutkan bahwa Anda harus menghindari metode get / set di kolom bulan lalu, "Why extends Is Evil."

Meskipun metode pengambil / penyetel adalah hal yang umum di Java, metode ini tidak berorientasi objek (OO). Faktanya, mereka dapat merusak pemeliharaan kode Anda. Selain itu, kehadiran berbagai metode pengambil dan penyetel merupakan tanda bahaya bahwa program tersebut belum tentu dirancang dengan baik dari perspektif OO.

Artikel ini menjelaskan mengapa Anda tidak boleh menggunakan getter dan setter (dan kapan Anda bisa menggunakannya) dan menyarankan metodologi desain yang akan membantu Anda keluar dari mentalitas pengambil / penyetel.

Tentang sifat desain

Sebelum saya meluncurkan ke kolom terkait desain lainnya (dengan judul yang provokatif, tidak kurang), saya ingin mengklarifikasi beberapa hal.

Saya terperangah oleh beberapa komentar pembaca yang dihasilkan dari kolom bulan lalu, "Why extends Is Evil" (lihat Komentar Balik di halaman terakhir artikel). Beberapa orang percaya saya berpendapat bahwa orientasi objek itu buruk hanya karena extendsmemiliki masalah, seolah-olah kedua konsep itu setara. Bukan itu yang saya pikir saya katakan, jadi izinkan saya mengklarifikasi beberapa masalah meta.

Kolom ini dan artikel bulan lalu membahas tentang desain. Desain, pada dasarnya, adalah serangkaian trade-off. Setiap pilihan memiliki sisi baik dan buruk, dan Anda membuat pilihan dalam konteks kriteria keseluruhan yang ditentukan oleh kebutuhan. Baik dan buruk bukanlah hal yang mutlak. Keputusan yang baik dalam satu konteks mungkin buruk di konteks lain.

Jika Anda tidak memahami kedua sisi suatu masalah, Anda tidak dapat membuat pilihan yang cerdas; faktanya, jika Anda tidak memahami semua konsekuensi dari tindakan Anda, Anda sama sekali tidak mendesain. Anda tersandung dalam kegelapan. Bukan kebetulan bahwa setiap bab dalam buku Pola Desain Gang of Four menyertakan bagian "Konsekuensi" yang menjelaskan kapan dan mengapa menggunakan pola tidak tepat.

Menyatakan bahwa beberapa fitur bahasa atau idiom pemrograman umum (seperti pengakses) memiliki masalah tidak sama dengan mengatakan Anda tidak boleh menggunakannya dalam keadaan apa pun. Dan hanya karena fitur atau idiom umum digunakan tidak berarti Anda juga harus menggunakannya. Pemrogram yang kurang informasi menulis banyak program dan hanya digunakan oleh Sun Microsystems atau Microsoft tidak secara ajaib meningkatkan kemampuan pemrograman atau desain seseorang. Paket Java berisi banyak kode bagus. Tetapi ada juga bagian dari kode itu yang saya yakin penulis malu untuk mengakui yang mereka tulis.

Dengan cara yang sama, pemasaran atau insentif politik sering kali mendorong idiom desain. Kadang-kadang programmer membuat keputusan yang buruk, tetapi perusahaan ingin mempromosikan apa yang dapat dilakukan oleh teknologi, jadi mereka tidak menekankan bahwa cara Anda melakukannya kurang dari ideal. Mereka memanfaatkan situasi yang buruk. Akibatnya, Anda bertindak tidak bertanggung jawab saat Anda mengadopsi praktik pemrograman apa pun hanya karena "begitulah seharusnya Anda melakukan sesuatu." Banyak proyek Enterprise JavaBeans (EJB) yang gagal membuktikan prinsip ini. Teknologi berbasis EJB adalah teknologi hebat jika digunakan dengan tepat, tetapi secara harfiah dapat menjatuhkan perusahaan jika digunakan secara tidak tepat.

Maksud saya adalah Anda tidak boleh memprogram secara membabi buta. Anda harus memahami kerusakan yang dapat ditimbulkan oleh fitur atau idiom. Dengan melakukannya, Anda berada dalam posisi yang jauh lebih baik untuk memutuskan apakah Anda harus menggunakan fitur atau idiom itu. Pilihan Anda harus diinformasikan dan pragmatis. Tujuan artikel ini adalah untuk membantu Anda mendekati pemrograman Anda dengan mata terbuka.

Abstraksi data

Prinsip dasar sistem OO adalah bahwa objek tidak boleh mengekspos detail implementasinya. Dengan cara ini, Anda dapat mengubah implementasi tanpa mengubah kode yang menggunakan objek tersebut. Selanjutnya, dalam sistem OO Anda harus menghindari fungsi pengambil dan penyetel karena sebagian besar menyediakan akses ke detail implementasi.

Untuk mengetahui alasannya, pertimbangkan bahwa mungkin ada 1.000 panggilan ke suatu getX()metode dalam program Anda, dan setiap panggilan mengasumsikan bahwa nilai yang dikembalikan adalah tipe tertentu. Anda mungkin menyimpan getX()nilai kembali dalam variabel lokal, misalnya, dan jenis variabel itu harus cocok dengan jenis nilai kembali. Jika Anda perlu mengubah cara objek diimplementasikan sedemikian rupa sehingga tipe X berubah, Anda berada dalam masalah besar.

Jika X sebelumnya adalah int, tetapi sekarang harus a long, Anda akan mendapatkan 1.000 kesalahan kompilasi. Jika Anda salah memperbaiki masalah dengan mentransmisikan nilai kembali ke int, kode akan dikompilasi dengan rapi, tetapi tidak akan berhasil. (Nilai kembali mungkin terpotong.) Anda harus mengubah kode yang mengelilingi setiap 1.000 panggilan tersebut untuk mengimbangi perubahan. Saya tentu tidak ingin melakukan pekerjaan sebanyak itu.

Salah satu prinsip dasar sistem OO adalah abstraksi data . Anda harus benar-benar menyembunyikan cara sebuah objek mengimplementasikan penangan pesan dari program lainnya. Itulah salah satu alasan mengapa semua variabel instance Anda (kolom nonkonstan kelas) harus private.

Jika Anda membuat variabel instan public, maka Anda tidak dapat mengubah kolom saat kelas berkembang seiring waktu karena Anda akan merusak kode eksternal yang menggunakan kolom tersebut. Anda tidak ingin mencari 1.000 penggunaan kelas hanya karena Anda mengubah kelas itu.

Prinsip penyembunyian implementasi ini mengarah ke pengujian asam yang baik dari kualitas sistem OO: Dapatkah Anda membuat perubahan besar-besaran pada definisi kelas — bahkan membuang semuanya dan menggantinya dengan implementasi yang sama sekali berbeda — tanpa memengaruhi kode apa pun yang menggunakan itu objek kelas? Modularisasi semacam ini adalah premis utama dari orientasi objek dan membuat pemeliharaan lebih mudah. Tanpa menyembunyikan implementasi, tidak ada gunanya menggunakan fitur OO lainnya.

Metode pengambil dan penyetel (juga dikenal sebagai publicpengakses ) berbahaya karena alasan yang sama dengan bidang berbahaya: Mereka memberikan akses eksternal ke detail implementasi. Bagaimana jika Anda perlu mengubah jenis bidang yang diakses? Anda juga harus mengubah jenis pengembalian pengakses. Anda menggunakan nilai pengembalian ini di banyak tempat, jadi Anda juga harus mengubah semua kode itu. Saya ingin membatasi efek perubahan ke definisi kelas tunggal. Saya tidak ingin mereka ikut campur dalam keseluruhan program.

Karena pengakses melanggar prinsip enkapsulasi, Anda dapat berargumen bahwa sistem yang menggunakan pengakses secara berat atau tidak tepat tidak berorientasi objek. Jika Anda melalui proses desain, bukan hanya pengkodean, Anda akan menemukan hampir tidak ada aksesor dalam program Anda. Prosesnya penting. Saya ingin mengatakan lebih banyak tentang masalah ini di akhir artikel.

Kurangnya metode pengambil / penyetel tidak berarti bahwa beberapa data tidak mengalir melalui sistem. Meskipun demikian, yang terbaik adalah meminimalkan pergerakan data sebanyak mungkin. Pengalaman saya adalah bahwa pemeliharaan berbanding terbalik dengan jumlah data yang bergerak di antara objek. Meskipun Anda mungkin belum mengetahui caranya, Anda sebenarnya dapat menghilangkan sebagian besar pergerakan data ini.

Dengan mendesain secara hati-hati dan berfokus pada apa yang harus Anda lakukan daripada bagaimana Anda akan melakukannya, Anda menghilangkan sebagian besar metode pengambil / penyetel dalam program Anda. Jangan meminta informasi yang Anda butuhkan untuk melakukan pekerjaan itu; meminta objek yang memiliki informasi untuk melakukan pekerjaan untuk Anda.Kebanyakan pengakses menemukan jalan mereka ke dalam kode karena perancang tidak memikirkan model dinamis: objek runtime dan pesan yang mereka kirim satu sama lain untuk melakukan pekerjaan itu. Mereka mulai (salah) dengan mendesain hierarki kelas dan kemudian mencoba memasukkan kelas-kelas itu ke dalam model dinamis. Pendekatan ini tidak pernah berhasil. Untuk membuat model statis, Anda perlu menemukan hubungan antar kelas, dan hubungan ini sama persis dengan aliran pesan. Pengaitan ada antara dua kelas hanya jika objek dari satu kelas mengirim pesan ke objek kelas lainnya. Tujuan utama model statis adalah untuk menangkap informasi asosiasi ini saat Anda membuat model secara dinamis.

Tanpa model dinamis yang didefinisikan dengan jelas, Anda hanya menebak-nebak bagaimana Anda akan menggunakan objek kelas. Akibatnya, metode pengakses sering kali berakhir di model karena Anda harus menyediakan akses sebanyak mungkin karena Anda tidak dapat memprediksi apakah Anda akan membutuhkannya atau tidak. Strategi desain-dengan-menebak semacam ini paling tidak efisien. Anda membuang waktu menulis metode yang tidak berguna (atau menambahkan kemampuan yang tidak perlu ke kelas).

Asesoris juga berakhir dalam desain karena kekuatan kebiasaan. Ketika programmer prosedural mengadopsi Java, mereka cenderung memulai dengan membangun kode yang sudah dikenal. Bahasa prosedural tidak memiliki kelas, tetapi mereka memiliki C struct(pikirkan: kelas tanpa metode). Maka, tampak wajar untuk meniru a structdengan membangun definisi kelas dengan hampir tanpa metode dan hanya publicbidang. Pemrogram prosedural ini membaca di suatu tempat bahwa bidang seharusnya private, jadi mereka membuat bidang privatedan menyediakan publicmetode pengakses. Tapi mereka hanya memperumit akses publik. Mereka tentunya belum membuat sistem berorientasi objek.

Gambar dirimu sendiri

Salah satu konsekuensi dari enkapsulasi bidang penuh adalah dalam konstruksi antarmuka pengguna (UI). Jika Anda tidak dapat menggunakan pengakses, Anda tidak dapat meminta kelas pembuat UI memanggil suatu getAttribute()metode. Sebaliknya, kelas memiliki elemen seperti drawYourself(...)metode.

Sebuah getIdentity()metode juga bisa bekerja, tentu saja, asalkan mengembalikan objek yang mengimplementasikan Identityantarmuka. Antarmuka ini harus menyertakan metode drawYourself()(atau beri-saya-a- JComponent-yang-mewakili-identitas-Anda). Meskipun getIdentitydimulai dengan "get", ini bukan aksesor karena tidak hanya mengembalikan bidang. Ini mengembalikan objek kompleks yang memiliki perilaku yang wajar. Bahkan ketika saya memiliki suatu Identityobjek, saya masih tidak tahu bagaimana identitas direpresentasikan secara internal.

Tentu saja, drawYourself()strategi berarti saya (terkesiap!) Memasukkan kode UI ke dalam logika bisnis. Pertimbangkan apa yang terjadi jika persyaratan UI berubah. Katakanlah saya ingin merepresentasikan atribut dengan cara yang sama sekali berbeda. Saat ini sebuah "identitas" adalah sebuah nama; besok adalah nama dan nomor ID; Sehari setelah itu ada nama, nomor ID, dan gambar. Saya membatasi ruang lingkup perubahan ini ke satu tempat dalam kode. Jika saya memiliki kelas beri-saya-yang-yang JComponent-mewakili-identitas-Anda, maka saya telah mengisolasi cara identitas direpresentasikan dari seluruh sistem.

Ingatlah bahwa saya belum benar-benar memasukkan kode UI apa pun ke dalam logika bisnis. Saya telah menulis lapisan UI dalam istilah AWT (Abstract Window Toolkit) atau Swing, yang keduanya merupakan lapisan abstraksi. Kode UI sebenarnya ada dalam implementasi AWT / Swing. Itulah inti dari lapisan abstraksi — untuk mengisolasi logika bisnis Anda dari mekanisme subsistem. Saya dapat dengan mudah port ke lingkungan grafis lain tanpa mengubah kode, jadi satu-satunya masalah adalah sedikit kekacauan. Anda dapat dengan mudah menghilangkan kekacauan ini dengan memindahkan semua kode UI ke kelas dalam (atau dengan menggunakan pola desain Façade).

JavaBeans

Anda mungkin keberatan dengan mengatakan, "Tapi bagaimana dengan JavaBeans?" Bagaimana dengan mereka? Anda pasti bisa membangun JavaBeans tanpa getter dan setter. The BeanCustomizer, BeanInfodan BeanDescriptorkelas semua yang ada untuk persis tujuan ini. Para desainer spesifikasi JavaBean melemparkan idiom pengambil / penyetel ke dalam gambar karena mereka pikir itu akan menjadi cara mudah untuk membuat kacang dengan cepat — sesuatu yang dapat Anda lakukan saat Anda belajar bagaimana melakukannya dengan benar. Sayangnya, tidak ada yang melakukannya.

Aksesor dibuat hanya sebagai cara untuk menandai properti tertentu sehingga program pembuat UI atau yang setara dapat mengidentifikasinya. Anda tidak seharusnya menyebut metode ini sendiri. Mereka ada untuk alat otomatis untuk digunakan. Alat ini menggunakan API introspeksi di Classkelas untuk menemukan metode dan mengekstrapolasi keberadaan properti tertentu dari nama metode. Dalam praktiknya, idiom berbasis introspeksi ini belum berhasil. Itu membuat kode menjadi terlalu rumit dan prosedural. Pemrogram yang tidak memahami abstraksi data sebenarnya memanggil pengakses, dan akibatnya, kode menjadi kurang dapat dipelihara. Untuk alasan ini, fitur metadata akan dimasukkan ke dalam Java 1.5 (dijadwalkan pada pertengahan 2004). Jadi, alih-alih:

milik pribadi int; public int getProperty () {return property; } public void setProperty (nilai int} {property = value;}

Anda akan dapat menggunakan sesuatu seperti:

private @property int property; 

Alat konstruksi UI atau yang setara akan menggunakan API introspeksi untuk menemukan properti, daripada memeriksa nama metode dan menyimpulkan keberadaan properti dari sebuah nama. Oleh karena itu, tidak ada aksesor runtime yang merusak kode Anda.

Kapan pengakses baik-baik saja?

Pertama, seperti yang saya bahas sebelumnya, tidak masalah untuk metode mengembalikan objek dalam bentuk antarmuka yang diimplementasikan objek karena antarmuka itu mengisolasi Anda dari perubahan ke kelas pelaksana. Metode semacam ini (yang mengembalikan referensi antarmuka) sebenarnya bukan "pengambil" dalam arti metode yang hanya menyediakan akses ke bidang. Jika Anda mengubah implementasi internal penyedia, Anda cukup mengubah definisi objek yang dikembalikan untuk mengakomodasi perubahan. Anda masih melindungi kode eksternal yang menggunakan objek melalui antarmukanya.