Kelas utilitas itu jahat? [Tutup]

97

Saya melihat utas ini

Jika kelas "Utilities" jahat, di mana saya harus meletakkan kode generik saya?

dan berpikir mengapa kelas utilitas jahat?

Katakanlah saya memiliki model domain yang terdiri dari puluhan kelas. Saya harus mampu untuk xml-ify instance. Apakah saya membuat metode toXml pada induknya? Apakah saya membuat kelas pembantu MyDomainXmlUtility.toXml? Ini adalah kasus di mana kebutuhan bisnis mencakup seluruh model domain - apakah itu benar-benar termasuk sebagai metode contoh? Bagaimana jika ada sekumpulan metode tambahan pada fungsionalitas xml aplikasi?

hvgotcodes
sumber
27
Devaluasi istilah "jahat" itu jahat!
Matthew Lock
1
@ matthew saya mempertahankan persyaratan posting yang menjadi dasar pertanyaan saya ...;)
hvgotcodes
Kelas utilitas adalah ide yang buruk karena alasan yang sama bagi para lajang.
CurtainDog
1
Perdebatan tentang apakah akan memiliki metode toXML adalah salah satu yang berpusat pada model domain kaya versus anemia. codeflow.blogspot.com/2007/05/anemic-vs-rich-domain-models.html
James P.
@james, toXML hanyalah sebuah contoh ... bagaimana dengan beberapa fungsi regex yang digunakan di semua tempat? Seperti, Anda perlu melakukan beberapa hal dengan string di seluruh model domain Anda, tetapi Anda tidak dapat menggunakan subclass karena masalah utama lainnya yang menggunakan satu superclass Anda (di java)
hvgotcodes

Jawaban:

129

Kelas utilitas tidak sepenuhnya jahat, tetapi dapat melanggar prinsip-prinsip yang menyusun desain berorientasi objek yang baik. Dalam desain berorientasi objek yang baik, kebanyakan kelas harus mewakili satu hal dan semua atribut dan operasinya. Jika Anda menjalankan sesuatu, metode itu mungkin harus menjadi anggota dari benda itu.

Namun, ada kalanya Anda dapat menggunakan kelas utilitas untuk mengelompokkan sejumlah metode bersama - contohnya adalah java.util.Collectionskelas yang menyediakan sejumlah utilitas yang dapat digunakan di Koleksi Java apa pun. Ini tidak spesifik untuk satu jenis Koleksi tertentu, tetapi menerapkan algoritme yang dapat digunakan pada Koleksi apa pun.

Sungguh, yang perlu Anda lakukan adalah memikirkan desain Anda dan menentukan di mana cara yang paling masuk akal untuk meletakkan metode. Biasanya, ini sebagai operasi di dalam kelas. Namun, terkadang, ini memang sebagai kelas utilitas. Namun, ketika Anda menggunakan kelas utilitas, jangan hanya memasukkan metode acak ke dalamnya, melainkan, atur metode berdasarkan tujuan dan fungsionalitas.

Thomas Owens
sumber
berhubungan baik dengan artikel ini tentang memeriksa kelas utilitas / penolong terhadap prinsip SOLID oo juga readability.com/articles/rk1vvcqy
melaos
8
Jika bahasa tidak menawarkan mekanisme namespace selain kelas, Anda tidak punya pilihan selain menyalahgunakan kelas di mana namespace akan melakukannya. Di C ++, Anda bisa meletakkan fungsi berdiri sendiri, yang tidak terkait dengan fungsi lain, ke dalam namespace. Di Java, Anda harus memasukkannya ke dalam kelas sebagai anggota (kelas) statis.
Unslander Monica
1
Pengelompokan sebagai metode mungkin kanonik dari sudut pandang OOP. Namun, OOP jarang menjadi solusi (di antara pengecualian adalah arsitektur tingkat tinggi dan widget toolkit sebagai salah satu contoh di mana semantik kode yang sangat rusak yang digunakan kembali oleh pewarisan benar-benar cocok) dan umumnya metode meningkatkan kopling dan ketergantungan. Contoh sepele: jika Anda menyediakan metode printer / pemindai ke kelas Anda sebagai metode, Anda memasangkan kelas ke perpustakaan printer / pemindai. Plus, Anda memperbaiki jumlah penerapan yang mungkin menjadi satu penerapan yang efektif. Kecuali jika Anda memasukkan antarmuka dan semakin meningkatkan ketergantungan.
Jo Jadi
Di sisi lain, saya setuju dengan sentimen Anda "kelompok berdasarkan tujuan dan fungsionalitas". Mari kita lihat kembali contoh printer / pemindai, dan mulai dengan satu set kelas yang sesuai, desain untuk tujuan yang sama. Anda mungkin ingin menulis beberapa kode debug dan mendesain representasi tekstual untuk kelas Anda. Anda dapat mengimplementasikan printer debug Anda dalam satu file yang bergantung pada semua kelas tersebut dan perpustakaan printer. Kode non-debugging tidak akan dibebani dengan dependensi implementasi ini.
Jo Jadi
1
Kelas Util dan Helper adalah artefak yang tidak menguntungkan dari kendala semacam itu, dan mereka yang tidak memahami OOP atau tidak memahami domain masalah terus menulis kode prosedural.
bilelovitch
57

Saya pikir konsensus umum adalah bahwa kelas utilitas tidak jahat itu sendiri . Anda hanya perlu menggunakannya dengan bijaksana:

  • Rancang metode utilitas statis agar umum dan dapat digunakan kembali. Pastikan bahwa mereka tidak memiliki kewarganegaraan; yaitu tidak ada variabel statis.

  • Jika Anda memiliki banyak metode utilitas, partisi ke dalam kelas-kelas dengan cara yang akan memudahkan pengembang untuk menemukannya.

  • Jangan gunakan kelas utilitas di mana metode statis atau contoh di kelas domain akan menjadi solusi yang lebih baik. Misalnya, pertimbangkan jika metode dalam kelas dasar abstrak atau kelas pembantu instantiable akan menjadi solusi yang lebih baik.

  • Untuk Java 8 dan seterusnya, "metode default" di antarmuka mungkin merupakan opsi yang lebih baik daripada kelas utilitas. (Lihat Antarmuka dengan metode default vs kelas Abstrak di Java 8 misalnya.)


Cara lain untuk melihat Pertanyaan ini adalah dengan mengamati bahwa dalam Pertanyaan yang dikutip, "Jika kelas utilitas" jahat "" adalah argumen yang menarik. Ini seperti saya bertanya:

"Jika babi bisa terbang, haruskah saya membawa payung?".

Dalam pertanyaan di atas saya sebenarnya tidak mengatakan bahwa babi dapat terbang ... atau bahwa saya setuju dengan proposisi bahwa mereka dapat terbang.

Pernyataan khas "xyz is evil" adalah alat retoris yang dimaksudkan untuk membuat Anda berpikir dengan mengajukan sudut pandang yang ekstrim. Mereka jarang (jika pernah) dimaksudkan sebagai pernyataan fakta literal.

Stephen C
sumber
16

Kelas utilitas bermasalah karena gagal mengelompokkan tanggung jawab dengan data yang mendukungnya.

Namun mereka sangat berguna dan saya membangunnya sepanjang waktu baik sebagai struktur permanen atau sebagai batu loncatan selama refaktor yang lebih menyeluruh.

Dari perspektif Kode Bersih , kelas utilitas melanggar Tanggung Jawab Tunggal dan Prinsip Terbuka-Tertutup. Mereka memiliki banyak alasan untuk berubah dan dengan desain tidak dapat diperluas. Mereka seharusnya hanya ada selama refactoring sebagai cruft perantara.

Alain O'Dea
sumber
2
Saya akan menyebutnya masalah praktis karena misalnya di Jawa Anda tidak dapat membuat fungsi apa pun yang bukan merupakan metode di kelas. Dalam bahasa seperti itu, "kelas tanpa variabel dan hanya dengan metode statis" adalah idiom yang merupakan singkatan dari namespace dari fungsi utilitas ... Ketika dihadapkan dengan kelas seperti itu di Jawa, itu adalah IMHO tidak valid dan tidak ada gunanya untuk berbicara tentang kelas , meskipun classkata kunci digunakan. Itu dukun seperti namespace, itu berjalan seperti namespace - ini adalah satu ...
Unslander Monica
2
Maaf karena blak-blakan, tetapi "metode statis tanpa kewarganegaraan [...] eksentrik dalam sistem OOP [...] masalah filosofis" sangat salah arah. SANGAT BAGUS jika Anda dapat menghindari status karena itu membuatnya mudah untuk menulis kode yang benar. RUSAK ketika saya harus menulis x.equals(y)karena 1) fakta bahwa ini benar-benar tidak boleh mengubah keadaan apa pun tidak disampaikan (dan saya tidak dapat mengandalkannya tanpa mengetahui penerapannya) 2) Saya tidak pernah bermaksud untuk memasukkan x" disukai posisi "atau" ditindaklanjuti "dibandingkan dengan y3) Saya tidak ingin mempertimbangkan" identitas " xatau ytetapi saya hanya tertarik pada nilai-nilai mereka.
Jo Jadi
4
Sungguh, sebagian besar fungsi di dunia nyata paling baik diekspresikan sebagai fungsi statis, tanpa kewarganegaraan, dan matematika. Apakah Math.PI objek yang harus dimutasi? Apakah saya benar-benar harus membuat instance AbstractSineCalculator yang mengimplementasikan IAbstractRealOperation hanya untuk mendapatkan sinus nomor?
Jo So
1
@JoSo Saya sangat setuju dengan nilai dari keadaan tanpa kewarganegaraan dan perhitungan langsung. Naive OO secara filosofis menentang ini dan saya pikir itu membuatnya sangat cacat. Ini mendorong abstraksi hal-hal yang tidak membutuhkannya (seperti yang telah Anda tunjukkan) dan gagal memberikan abstraksi yang berarti untuk komputasi aktual. Namun, pendekatan yang seimbang untuk menggunakan bahasa OO cenderung ke arah kekekalan dan keadaan tanpa kewarganegaraan karena mereka mempromosikan modularitas dan pemeliharaan.
Alain O'Dea
2
@ AlainO'Dea: Saya menyadari bahwa saya salah membaca komentar Anda karena menentang fungsi tanpa kewarganegaraan. Saya minta maaf atas nada saya - saya saat ini terlibat dalam proyek Java pertama saya (setelah saya menghindari OOP untuk sebagian besar dari 10 tahun pemrograman saya) dan terkubur di bawah lapisan hal-hal abstrak dengan keadaan dan makna yang tidak jelas. Dan saya hanya butuh udara segar :-).
Jo Jadi
9

Saya kira itu mulai menjadi jahat ketika

1) Terlalu besar (kelompokkan saja ke dalam kategori yang bermakna dalam kasus ini).
2) Metode yang tidak boleh menjadi metode statis

Tapi selama kondisi ini tidak terpenuhi, menurut saya itu sangat berguna.

Enno Shioji
sumber
"Ada metode yang seharusnya bukan metode statis." Bagaimana ini mungkin?
Koray Tugay
@KorayTug Pertimbangkan yang non-statis Widget.compareTo(Widget widget)versus statis WidgetUtils.compare(Widget widgetOne, Widget widgetTwo). Perbandingan adalah contoh dari sesuatu yang tidak boleh dilakukan secara statis.
ndm13
5

Aturan praktis

Anda dapat melihat masalah ini dari dua perspektif:

  • *UtilMetode keseluruhan sering kali merupakan saran dari desain kode yang buruk atau konvensi penamaan yang malas.
  • Ini adalah solusi desain yang sah untuk fungsi tanpa kewarganegaraan lintas-domain yang dapat digunakan kembali. Harap dicatat bahwa untuk hampir semua masalah umum ada solusi yang ada.

Contoh 1. Penggunaan utilkelas / modul dengan benar. Contoh perpustakaan eksternal

Anggaplah Anda sedang menulis aplikasi yang mengelola pinjaman dan kartu kredit. Data dari modul ini diekspos melalui layanan web dalam jsonformat. Secara teori, Anda dapat secara manual mengonversi objek menjadi string yang akan ada di dalamnya json, tetapi itu akan menciptakan kembali roda. Solusi yang benar adalah dengan memasukkan kedua modul perpustakaan eksternal yang digunakan untuk mengonversi objek java ke format yang diinginkan. (dalam contoh gambar saya telah menunjukkan gson )

masukkan deskripsi gambar di sini


Contoh 2. Penggunaan utilkelas / modul dengan benar. Menulis sendiri utiltanpa alasan kepada anggota tim lainnya

Sebagai kasus penggunaan, asumsikan bahwa kita perlu melakukan beberapa perhitungan dalam dua modul aplikasi, tetapi keduanya perlu mengetahui kapan ada hari libur nasional di Polandia. Secara teori, Anda dapat membuat kalkulasi ini di dalam modul, tetapi akan lebih baik jika fungsi ini diekstrak ke modul terpisah.

Ini detail kecil, tapi penting. Kelas / modul yang telah Anda tulis tidak disebut HolidayUtil, tetapi PolishHolidayCalculator. Secara fungsional ini adalah utilkelas, tetapi kami telah berhasil menghindari kata umum.

masukkan deskripsi gambar di sini

Marcin Szymczak
sumber
1
Penggemar yEd saya melihat;) Aplikasi luar biasa.
Sridhar Sarnobat
3

Kelas utilitas buruk karena berarti Anda terlalu malas untuk memikirkan nama yang lebih baik untuk kelas tersebut :)

Itu dikatakan, saya malas. Terkadang Anda hanya perlu menyelesaikan pekerjaan dan pikiran Anda kosong .. saat itulah kelas "Utilitas" mulai masuk.

Larry Watanabe
sumber
3

Melihat kembali pertanyaan ini sekarang, saya akan mengatakan bahwa metode ekstensi C # benar-benar menghancurkan kebutuhan akan kelas utilitas. Tetapi tidak semua bahasa memiliki konstruksi yang begitu jenius.

Anda juga memiliki JavaScript, di mana Anda bisa menambahkan fungsi baru langsung ke objek yang ada.

Tapi saya tidak yakin benar-benar ada cara elegan untuk menyelesaikan masalah ini dalam bahasa yang lebih lama seperti C ++ ...

Kode OO yang baik agak sulit untuk ditulis, dan sulit ditemukan karena menulis OO yang baik membutuhkan lebih banyak waktu / pengetahuan daripada menulis kode fungsional yang layak.

Dan jika anggaran Anda terbatas, atasan Anda tidak selalu senang melihat Anda menghabiskan sepanjang hari menulis banyak kelas ...

HumbleWebDev
sumber
3

Saya tidak sepenuhnya setuju bahwa kelas utilitas itu jahat.

Meskipun kelas utilitas dapat melanggar prinsip-prinsip OO dalam beberapa hal, mereka tidak selalu buruk.

Misalnya, bayangkan Anda menginginkan fungsi yang akan membersihkan string dari semua substring yang cocok dengan nilai x.

stl c ++ (seperti yang sekarang) tidak mendukung ini secara langsung.

Anda dapat membuat ekstensi polimorfik std::string.

Tetapi masalahnya adalah, apakah Anda benar-benar ingin SETIAP string yang Anda gunakan dalam proyek Anda menjadi kelas string yang diperluas?

Ada kalanya OO tidak masuk akal, dan ini salah satunya. Kami ingin program kami kompatibel dengan program lain, jadi kami akan tetap menggunakan std::stringdan membuat kelas StringUtil_(atau sesuatu).

Saya akan mengatakan yang terbaik jika Anda tetap dengan satu util per kelas. Menurut saya konyol memiliki satu util untuk semua kelas atau banyak utilitas untuk satu kelas.

HumbleWebDev
sumber
2

Sangat mudah untuk memberi merek pada suatu utilitas hanya karena perancang tidak dapat memikirkan tempat yang tepat untuk meletakkan kode. Seringkali hanya ada sedikit "utilitas" yang benar.

Sebagai aturan praktis, saya biasanya menyimpan kode dalam paket tempat kode tersebut pertama kali digunakan, dan kemudian hanya melakukan refactor ke tempat yang lebih umum jika saya menemukan bahwa nantinya kode tersebut benar-benar diperlukan di tempat lain. Satu-satunya pengecualian adalah jika saya sudah memiliki paket yang melakukan fungsi serupa / terkait, dan kode paling cocok di sana.

mdma
sumber
2

Kelas utilitas yang berisi metode statis stateless dapat berguna. Ini seringkali sangat mudah untuk pengujian unit.

seand
sumber
1

Dengan Java 8 Anda dapat menggunakan metode statis di antarmuka ... masalah terpecahkan.

Marc T
sumber
Itu tidak mengatasi masalah yang dinyatakan dalam stackoverflow.com/a/3340037/2859065
Luchostein
Apa bedanya dengan menempatkan mereka di kelas dan mengimpor?
wilmol
1

Kebanyakan kelas Util buruk karena:

  1. Mereka memperluas cakupan metode. Mereka membuat kode publik yang sebaliknya akan menjadi pribadi. Jika metode util diperlukan oleh banyak pemanggil dalam kelas yang terpisah dan stabil (yaitu tidak perlu diperbarui), lebih baik menurut saya untuk menyalin dan menempel metode pembantu pribadi ke dalam kelas pemanggil. Setelah Anda mengeksposnya sebagai API, Anda membuatnya lebih sulit untuk memahami apa titik masuk publik ke komponen jar (Anda mempertahankan struktur pohon yang disebut hierarki dengan satu induk per metode. Ini lebih mudah untuk memisahkan mental ke dalam komponen yang lebih sulit ketika Anda memiliki metode yang dipanggil dari beberapa metode induk).
  2. Mereka menghasilkan kode mati. Metode pemanfaatan dari waktu ke waktu menjadi tidak digunakan saat aplikasi Anda berkembang dan Anda berakhir dengan kode yang tidak digunakan yang mengotori basis kode Anda. Jika tetap privat, kompilator Anda akan memberi tahu Anda bahwa metode ini tidak digunakan dan Anda bisa menghapusnya (kode terbaik adalah tidak ada kode sama sekali). Setelah Anda membuat metode non-privat, komputer Anda tidak akan berdaya untuk membantu Anda menghapus kode yang tidak digunakan. Ini dapat dipanggil dari file jar yang berbeda untuk semua komputer yang tahu.

Ada beberapa analogi pustaka statis vs dinamis.

Sridhar Sarnobat
sumber
0

Ketika saya tidak dapat menambahkan metode ke kelas (katakanlah, Accountdikunci terhadap perubahan oleh Pengembang Jr), saya hanya menambahkan beberapa metode statis ke kelas Utilitas saya seperti:

public static int method01_Account(Object o, String... args) {
    Account acc = (Account)o;
    ...
    return acc.getInt();
}  
Tuan White
sumber
1
Sepertinya Anda adalah anak Jr bagi saya ..: P Cara tidak jahat untuk melakukan ini adalah dengan sopan meminta "Pengembang Jr" Anda untuk membuka kunci Accountfile. Atau lebih baik, gunakan sistem kontrol sumber tanpa penguncian.
Sudeep
1
Saya berasumsi yang dia maksud bahwa Accountfile tersebut dikunci sehingga Jr. Pengembang tidak dapat mengubahnya. Sebagai seorang Jr Developer, untuk menyiasatinya, dia melakukan seperti di atas. Meskipun demikian, masih lebih baik mendapatkan akses tulis ke file dan melakukannya dengan benar.
Doc
Menambahkan +1 untuk ini karena suara negatif tampak kasar ketika tidak ada yang mengetahui konteks lengkap mengapa Anda memberikan jawaban ini
joelc
0

Kelas utilitas tidak selalu jahat. Tetapi mereka seharusnya hanya berisi metode yang umum di berbagai fungsi. Jika ada metode yang hanya dapat digunakan di antara sejumlah kelas, pertimbangkan untuk membuat kelas abstrak sebagai induk bersama dan letakkan metode di dalamnya.

Sid J
sumber