Kadang-kadang saya menemukan metode dengan jumlah parameter yang tidak nyaman. Lebih sering daripada tidak, mereka tampak seperti konstruktor. Sepertinya harus ada cara yang lebih baik, tapi saya tidak bisa melihat apa itu.
return new Shniz(foo, bar, baz, quux, fred, wilma, barney, dino, donkey)
Saya telah berpikir untuk menggunakan struct untuk mewakili daftar parameter, tetapi itu sepertinya mengalihkan masalah dari satu tempat ke tempat lain, dan membuat tipe lain dalam prosesnya.
ShnizArgs args = new ShnizArgs(foo, bar, baz, quux, fred, wilma, barney, dino, donkey)
return new Shniz(args);
Jadi itu sepertinya bukan peningkatan. Jadi apa pendekatan terbaik?
refactoring
rekursif
sumber
sumber
Jawaban:
Cara terbaik adalah menemukan cara untuk mengelompokkan argumen menjadi satu. Ini mengasumsikan, dan benar-benar hanya berfungsi jika, Anda akan berakhir dengan beberapa "pengelompokan" argumen.
Misalnya, jika Anda mengirimkan spesifikasi untuk sebuah persegi panjang, Anda dapat memasukkan x, y, width, dan height atau Anda bisa mengirimkan objek persegi panjang yang berisi x, y, width, dan height.
Carilah hal-hal seperti ini saat refactoring untuk membersihkannya. Jika argumen benar-benar tidak dapat digabungkan, mulailah melihat apakah Anda telah melanggar Prinsip Tanggung Jawab Tunggal.
sumber
Saya akan menganggap yang Anda maksud C # . Beberapa dari hal ini juga berlaku untuk bahasa lain.
Anda memiliki beberapa opsi:
beralih dari konstruktor ke penyetel properti . Ini dapat membuat kode lebih mudah dibaca, karena jelas bagi pembaca nilai mana yang sesuai dengan parameter mana. Sintaks Object Initializer membuatnya terlihat bagus. Ini juga mudah diterapkan, karena Anda cukup menggunakan properti yang dibuat secara otomatis dan melewati penulisan konstruktor.
Namun, Anda kehilangan keabadian, dan Anda kehilangan kemampuan untuk memastikan bahwa nilai yang diperlukan disetel sebelum menggunakan objek pada waktu kompilasi.
Pola Pembangun .
Pikirkan tentang hubungan antara
string
danStringBuilder
. Anda bisa mendapatkan ini untuk kelas Anda sendiri. Saya suka menerapkannya sebagai kelas bersarang, jadi kelasC
memiliki kelas terkaitC.Builder
. Saya juga menyukai antarmuka yang fasih pada pembangun. Jika dilakukan dengan benar, Anda bisa mendapatkan sintaks seperti ini:Saya memiliki skrip PowerShell yang memungkinkan saya menghasilkan kode pembangun untuk melakukan semua ini, di mana inputnya terlihat seperti:
Jadi saya bisa menghasilkan pada waktu kompilasi.
partial
kelas biarkan saya memperluas kelas utama dan pembangun tanpa mengubah kode yang dihasilkan.Pemfaktoran ulang "Perkenalkan Objek Parameter" . Lihat Katalog Refactoring . Idenya adalah Anda mengambil beberapa parameter yang Anda teruskan dan memasukkannya ke tipe baru, lalu meneruskan instance dari tipe itu. Jika Anda melakukan ini tanpa berpikir, Anda akan kembali ke tempat Anda memulai:
menjadi
Namun, pendekatan ini memiliki potensi terbesar untuk memberikan dampak positif pada kode Anda. Jadi, lanjutkan dengan mengikuti langkah-langkah berikut:
Cari subset parameter yang masuk akal jika digabungkan. Mengelompokkan semua parameter fungsi secara sembarangan tidak akan menghasilkan banyak hasil; tujuannya adalah untuk memiliki pengelompokan yang masuk akal. Anda akan tahu bahwa Anda melakukannya dengan benar ketika nama tipe baru sudah jelas.
Cari tempat lain di mana nilai-nilai ini digunakan bersama, dan gunakan tipe baru di sana juga. Kemungkinannya adalah, ketika Anda menemukan tipe baru yang bagus untuk sekumpulan nilai yang sudah Anda gunakan di semua tempat, tipe baru itu juga akan masuk akal di semua tempat itu.
Cari fungsionalitas yang ada di kode yang ada, tetapi termasuk dalam tipe baru.
Misalnya, mungkin Anda melihat beberapa kode yang terlihat seperti:
Anda bisa mengambil
minSpeed
danmaxSpeed
parameter dan menempatkan mereka dalam jenis baru:Ini lebih baik, tetapi untuk benar-benar memanfaatkan tipe baru, pindahkan perbandingan ke tipe baru:
Dan sekarang kita menuju ke suatu tempat: implementasi
SpeedIsAcceptable()
now mengatakan apa yang Anda maksud, dan Anda memiliki kelas yang berguna dan dapat digunakan kembali. (Langkah jelas berikutnya adalah membuatSpeedRange
keRange<Speed>
.)Seperti yang Anda lihat, Memperkenalkan Objek Parameter adalah awal yang baik, tetapi nilai sebenarnya adalah membantu kami menemukan jenis berguna yang telah hilang dari model kami.
sumber
Jika ini adalah konstruktor, terutama jika ada beberapa varian yang kelebihan muatan, Anda harus melihat pola Builder:
Jika ini adalah metode normal, Anda harus memikirkan tentang hubungan antara nilai yang diteruskan, dan mungkin membuat Objek Transfer.
sumber
Jawaban klasik untuk ini adalah dengan menggunakan kelas untuk merangkum beberapa, atau semua, parameter. Secara teori kedengarannya bagus, tapi saya adalah tipe orang yang membuat kelas untuk konsep yang memiliki makna dalam domain tersebut, jadi tidak selalu mudah untuk menerapkan saran ini.
Misalnya, alih-alih:
Anda bisa menggunakan
YMMV
sumber
Ini dikutip dari buku Fowler and Beck: "Refactoring"
sumber
Saya tidak ingin terdengar seperti orang yang bijak, tetapi Anda juga harus memeriksa untuk memastikan data yang Anda berikan benar - benar harus diedarkan: Mengirimkan barang ke konstruktor (atau metode dalam hal ini) berbau agak mirip sedikit penekanan pada perilaku suatu objek.
Jangan salah paham: Metode dan konstruktor akan memiliki banyak parameter kadang-kadang. Tetapi ketika ditemui, coba pertimbangkan untuk merangkum data dengan perilaku sebagai gantinya.
Jenis bau ini (karena kita berbicara tentang pemfaktoran ulang, kata mengerikan ini sepertinya tepat ...) mungkin juga terdeteksi untuk objek yang memiliki banyak properti atau getter / setter (baca: apa saja).
sumber
Ketika saya melihat daftar parameter yang panjang, pertanyaan pertama saya adalah apakah fungsi atau objek ini bekerja terlalu banyak. Mempertimbangkan:
Tentu saja contoh ini sengaja konyol, tetapi saya telah melihat banyak program nyata dengan contoh hanya sedikit kurang konyol, di mana satu kelas digunakan untuk menampung banyak hal yang hampir tidak terkait atau tidak terkait, tampaknya hanya karena program pemanggil yang sama membutuhkan keduanya atau karena programmer kebetulan memikirkan keduanya pada saat yang bersamaan. Terkadang solusi yang mudah adalah dengan memecah kelas menjadi beberapa bagian yang masing-masing melakukan tugasnya sendiri.
Sedikit lebih rumit adalah ketika sebuah kelas benar-benar perlu menangani banyak hal logis, seperti pesanan pelanggan dan informasi umum tentang pelanggan. Dalam kasus ini, buat kelas untuk pelanggan dan kelas untuk pesanan, dan biarkan mereka berbicara satu sama lain jika perlu. Jadi, alih-alih:
Kami dapat memiliki:
Meskipun tentu saja saya lebih suka fungsi yang hanya mengambil 1 atau 2 atau 3 parameter, terkadang kita harus menerima bahwa, secara realistis, fungsi ini membutuhkan banyak, dan jumlah itu sendiri tidak benar-benar menciptakan kerumitan. Sebagai contoh:
Ya, itu sekumpulan bidang, tapi mungkin yang akan kita lakukan dengan mereka adalah menyimpannya ke catatan database atau melemparkannya ke layar atau semacamnya. Sebenarnya tidak banyak pemrosesan di sini.
Ketika daftar parameter saya menjadi panjang, saya lebih suka jika saya dapat memberikan field tipe data yang berbeda. Seperti ketika saya melihat fungsi seperti:
Dan kemudian saya melihatnya disebut dengan:
Saya khawatir. Melihat panggilan itu, sama sekali tidak jelas apa arti semua nomor, kode, dan bendera samar ini. Ini hanya meminta kesalahan. Seorang programmer mungkin dengan mudah bingung tentang urutan parameter dan secara tidak sengaja mengganti dua, dan jika mereka adalah tipe data yang sama, kompiler akan menerimanya. Saya lebih suka memiliki tanda tangan di mana semua hal ini adalah enum, jadi panggilan lewat dalam hal-hal seperti Type.ACTIVE daripada "A" dan CreditWatch.NO daripada "false", dll.
sumber
Jika beberapa parameter konstruktor bersifat opsional, masuk akal untuk menggunakan pembangun, yang akan mendapatkan parameter yang diperlukan dalam konstruktor, dan memiliki metode untuk yang opsional, mengembalikan pembangun, untuk digunakan seperti ini:
Rincian ini dijelaskan dalam Effective Java, 2nd Ed., Hal. 11. Untuk argumen metode, buku yang sama (p. 189) menjelaskan tiga pendekatan untuk memperpendek daftar parameter:
DinoDonkey
bukandino
dandonkey
sumber
Saya akan menggunakan konstruktor default dan pemukim properti. C # 3.0 memiliki sintaks yang bagus untuk melakukan ini secara otomatis.
Peningkatan kode datang dengan menyederhanakan konstruktor dan tidak harus mendukung banyak metode untuk mendukung berbagai kombinasi. Sintaks "pemanggilan" masih sedikit "bertele-tele", tetapi tidak lebih buruk daripada memanggil pemukim properti secara manual.
sumber
Anda belum memberikan informasi yang cukup untuk menjamin jawaban yang bagus. Daftar parameter yang panjang tidak selalu buruk.
dapat diartikan sebagai:
Dalam hal ini Anda lebih baik membuat kelas untuk merangkum parameter karena Anda memberi arti pada parameter yang berbeda dengan cara yang dapat diperiksa oleh kompilator serta membuat kode lebih mudah dibaca secara visual. Ini juga membuatnya lebih mudah untuk membaca dan memfaktor ulang nanti.
Atau jika Anda memiliki:
Ini adalah kasus yang jauh berbeda karena semua objek berbeda (dan tidak mungkin tercampur aduk). Setuju bahwa jika semua objek diperlukan, dan semuanya berbeda, tidak masuk akal untuk membuat kelas parameter.
Selain itu, apakah beberapa parameter bersifat opsional? Apakah ada penimpaan metode (nama metode yang sama, tetapi tanda tangan metode yang berbeda?) Detail semacam ini penting bagi jawaban terbaiknya .
* Tas properti juga bisa berguna, tetapi tidak secara khusus lebih baik karena tidak ada latar belakang yang diberikan.
Seperti yang Anda lihat, ada lebih dari 1 jawaban yang benar untuk pertanyaan ini. Ambil pilihanmu.
sumber
Anda dapat mencoba mengelompokkan parameter Anda menjadi beberapa struct / kelas yang berarti (jika memungkinkan).
sumber
Saya biasanya akan bersandar pada pendekatan struct - mungkin sebagian besar parameter ini terkait dalam beberapa cara dan mewakili keadaan beberapa elemen yang relevan dengan metode Anda.
Jika kumpulan parameter tidak dapat dibuat menjadi objek yang bermakna, itu mungkin merupakan tanda bahwa
Shniz
melakukan terlalu banyak, dan pemfaktoran ulang harus melibatkan pemecahan metode menjadi masalah yang terpisah.sumber
Anda dapat memperdagangkan kompleksitas untuk baris kode sumber. Jika metodenya sendiri melakukan terlalu banyak (pisau Swiss) coba bagi separuh tugasnya dengan membuat metode lain. Jika metodenya sederhana hanya membutuhkan terlalu banyak parameter maka yang disebut objek parameter adalah cara yang harus dilakukan.
sumber
Jika bahasa Anda mendukungnya, gunakan parameter bernama dan buat opsional sebanyak mungkin (dengan default yang masuk akal).
sumber
Saya pikir metode yang Anda jelaskan adalah cara untuk pergi. Ketika saya menemukan metode dengan banyak parameter dan / atau yang mungkin membutuhkan lebih banyak di masa depan, saya biasanya membuat objek ShnizParams untuk dilewati, seperti yang Anda gambarkan.
sumber
Bagaimana kalau tidak menyetelnya sekaligus di konstruktor tetapi melakukannya melalui properti / penyetel ? Saya telah melihat beberapa kelas .NET yang memanfaatkan pendekatan ini seperti
Process
kelas:sumber
Saya setuju dengan pendekatan memindahkan parameter ke objek parameter (struct). Daripada hanya menempelkan semuanya dalam satu objek, tinjau jika fungsi lain menggunakan grup parameter yang serupa. Objek paramater lebih berharga jika digunakan dengan beberapa fungsi yang Anda harapkan kumpulan parameter berubah secara konsisten di seluruh fungsi tersebut. Bisa jadi Anda hanya memasukkan beberapa parameter ke dalam objek parameter baru.
sumber
Jika Anda memiliki banyak parameter, kemungkinan besar metode tersebut melakukan terlalu banyak, jadi atasi ini terlebih dahulu dengan membagi metode menjadi beberapa metode yang lebih kecil. Jika Anda masih memiliki terlalu banyak parameter setelah ini coba kelompokkan argumen atau ubah beberapa parameter menjadi anggota instance.
Lebih suka kelas / metode kecil daripada besar. Ingatlah prinsip tanggung jawab tunggal.
sumber
Argumen yang diberi nama adalah opsi yang baik (dengan asumsi bahasa yang mendukungnya) untuk menghilangkan ambiguitas daftar parameter yang panjang (atau bahkan pendek!) Sambil juga mengizinkan (dalam kasus konstruktor) properti kelas menjadi tidak dapat diubah tanpa memaksakan persyaratan untuk mengizinkannya ada dalam keadaan sebagian dibangun.
Opsi lain yang akan saya cari dalam melakukan refactor semacam ini adalah grup parameter terkait yang mungkin lebih baik ditangani sebagai objek independen. Menggunakan kelas Persegi Panjang dari jawaban sebelumnya sebagai contoh, konstruktor yang mengambil parameter untuk x, y, tinggi, dan lebar dapat memfaktorkan x dan y menjadi objek Titik, memungkinkan Anda untuk meneruskan tiga parameter ke konstruktor Persegi Panjang. Atau melangkah lebih jauh dan menjadikannya dua parameter (UpperLeftPoint, LowerRightPoint), tetapi itu akan menjadi pemfaktoran ulang yang lebih radikal.
sumber
Itu tergantung pada jenis argumen yang Anda miliki, tetapi jika banyak nilai / opsi boolean mungkin Anda bisa menggunakan Flag Enum?
sumber
Saya pikir masalah itu sangat terkait dengan domain masalah yang Anda coba selesaikan dengan kelas.
Dalam beberapa kasus, konstruktor 7-parameter dapat menunjukkan hierarki kelas yang buruk: dalam kasus tersebut, struct / class helper yang disarankan di atas biasanya merupakan pendekatan yang baik, tetapi Anda juga cenderung berakhir dengan banyak struct yang hanya merupakan tas properti dan tidak melakukan sesuatu yang berguna. Konstruktor 8-argumen mungkin juga menunjukkan bahwa kelas Anda terlalu umum / terlalu serbaguna sehingga membutuhkan banyak opsi agar benar-benar berguna. Dalam hal ini, Anda dapat memfaktor ulang kelas atau mengimplementasikan konstruktor statis yang menyembunyikan konstruktor kompleks yang sebenarnya: mis. Shniz.NewBaz (foo, bar) sebenarnya dapat memanggil konstruktor sebenarnya dengan memberikan parameter yang tepat.
sumber
Salah satu pertimbangannya adalah nilai mana yang akan menjadi hanya-baca setelah objek dibuat?
Properti yang dapat ditulis secara publik mungkin dapat diberikan setelah konstruksi.
Dari mana asalnya nilai-nilai itu? Mungkin beberapa nilai benar-benar eksternal sedangkan yang lain benar-benar dari beberapa konfigurasi atau data global yang dikelola oleh pustaka.
Dalam hal ini Anda dapat menyembunyikan konstruktor dari penggunaan eksternal dan menyediakan fungsi Buat untuknya. Fungsi create mengambil nilai eksternal yang sebenarnya dan membuat objek, lalu menggunakan pengakses yang hanya tersedia di perpustakaan untuk menyelesaikan pembuatan objek.
Akan sangat aneh untuk memiliki objek yang membutuhkan 7 atau lebih parameter untuk memberikan objek keadaan lengkap dan semuanya benar-benar bersifat eksternal.
sumber
Ketika sebuah klas memiliki konstruktor yang mengambil terlalu banyak argumen, biasanya itu adalah tanda bahwa ia memiliki terlalu banyak tanggung jawab. Ini mungkin dapat dipecah menjadi kelas-kelas terpisah yang bekerja sama untuk memberikan fungsi yang sama.
Jika Anda benar-benar membutuhkan banyak argumen ke konstruktor, pola Builder dapat membantu Anda. Tujuannya adalah untuk tetap meneruskan semua argumen ke konstruktor, sehingga statusnya diinisialisasi dari awal dan Anda masih dapat membuat kelas tidak dapat diubah jika diperlukan.
Lihat di bawah :
sumber