Menjinakkan kelas 'fungsi utilitas'

15

Dalam basis kode Java kami, saya terus melihat pola berikut:

/**
 This is a stateless utility class
 that groups useful foo-related operations, often with side effects.
*/
public class FooUtil {
  public int foo(...) {...}
  public void bar(...) {...}
}

/**
 This class does applied foo-related things.
*/
class FooSomething {
  int DoBusinessWithFoo(FooUtil fooUtil, ...) {
    if (fooUtil.foo(...)) fooUtil.bar(...);
  }
}

Yang mengganggu saya adalah bahwa saya harus melewati contoh di FooUtilmana - mana, karena pengujian.

  • Saya tidak dapat membuat FooUtilmetode statis, karena saya tidak akan dapat mengejek mereka untuk pengujian keduanya FooUtildan kelas kliennya.
  • Saya tidak dapat membuat contoh FooUtildi tempat konsumsi dengan new, lagi karena saya tidak akan dapat mengejeknya untuk pengujian.

Saya kira bahwa taruhan terbaik saya adalah menggunakan injeksi (dan saya lakukan), tetapi ia menambahkan kerepotan sendiri. Juga, melewatkan beberapa instance util mengembang ukuran daftar parameter metode.

Apakah ada cara untuk menangani ini dengan lebih baik yang tidak saya lihat?

Pembaruan: karena kelas utilitas tidak memiliki kewarganegaraan, saya mungkin dapat menambahkan anggota statis statis INSTANCE, atau getInstance()metode statis , sambil mempertahankan kemampuan untuk memperbarui bidang statis yang mendasari kelas untuk pengujian. Sepertinya tidak super bersih juga.

9000
sumber
4
Dan asumsi Anda bahwa semua dependensi perlu diejek untuk melakukan pengujian unit secara efektif tidak benar (meskipun saya masih mencari pertanyaan yang membahas itu).
Telastyn
4
Mengapa metode ini bukan anggota kelas yang datanya mereka manipulasi?
Jules
3
Memiliki seperangkat Junit terpisah yang memanggil fns statis jika FooUtility. Maka Anda bisa menggunakannya tanpa mengejek. Jika itu sesuatu yang Anda perlu mengejek maka saya curiga bukan hanya utilitas tetapi melakukan beberapa IO atau logika bisnis dan sebenarnya harus menjadi referensi anggota kelas dan diinisialisasi melalui metode konstruktor, init atau setter.
tgkprog
2
+1 untuk komentar Jules. Kelas Util adalah bau kode. Bergantung pada semantiknya, harus ada solusi yang lebih baik. Mungkin FooUtilmetode harus dimasukkan ke dalam FooSomething, mungkin ada properti FooSomethingyang harus diubah / diperpanjang sehingga FooUtilmetode tidak diperlukan lagi. Tolong beri kami contoh yang lebih nyata tentang apa yang FooSomethingmembantu kami memberikan jawaban yang baik untuk pertanyaan ini.
valenterri
13
@valenterry: Satu-satunya alasan util kelas adalah kode bau adalah karena Java tidak menyediakan cara yang baik untuk memiliki fungsi mandiri. Ada alasan yang sangat bagus untuk memiliki fungsi yang tidak spesifik untuk kelas.
Robert Harvey

Jawaban:

16

Pertama-tama izinkan saya mengatakan bahwa ada pendekatan pemrograman yang berbeda untuk masalah yang berbeda. Untuk pekerjaan saya, saya lebih suka desain OO yang kuat untuk orginaisasi dan pemeliharaan dan jawaban saya mencerminkan hal ini. Pendekatan fungsional akan memiliki jawaban yang sangat berbeda.

Saya cenderung menemukan bahwa sebagian besar kelas utilitas dibuat karena objek metode seharusnya tidak ada. Ini hampir selalu berasal dari salah satu dari dua kasus, baik seseorang melewati koleksi di sekitar atau seseorang melewati kacang (Ini sering disebut POJO tapi saya percaya banyak setter dan getter digambarkan sebagai kacang).

Ketika Anda memiliki koleksi yang Anda bagikan, harus ada kode yang ingin menjadi bagian dari koleksi itu, dan ini akhirnya ditaburkan di seluruh sistem Anda dan akhirnya dikumpulkan ke dalam kelas utilitas.

Saran saya adalah untuk membungkus koleksi Anda (Atau kacang-kacangan) dengan data lain yang terkait erat ke dalam kelas baru dan memasukkan kode "utilitas" ke objek yang sebenarnya.

Anda dapat memperluas koleksi dan menambahkan metode di sana, tetapi Anda kehilangan kontrol atas keadaan objek Anda dengan cara itu - saya sarankan Anda merangkum sepenuhnya dan hanya mengekspos metode logika bisnis yang diperlukan (Pikirkan "Jangan minta benda Anda untuk data mereka, berikan perintah objek Anda dan biarkan mereka bertindak pada data pribadi mereka ", penyewa OO yang penting dan yang, ketika Anda memikirkannya, membutuhkan pendekatan yang saya sarankan di sini).

"Pembungkus" ini berguna untuk objek perpustakaan tujuan umum yang menyimpan data tetapi Anda tidak dapat menambahkan kode - Memasukkan kode dengan data yang dimanipulasi adalah konsep utama lain dalam desain OO.

Ada banyak gaya pengkodean di mana konsep pembungkus ini akan menjadi ide yang buruk - yang ingin menulis seluruh kelas ketika hanya menerapkan skrip atau tes satu kali? Tapi saya pikir enkapsulasi dari setiap jenis / koleksi dasar yang tidak dapat Anda tambahkan kode untuk diri sendiri adalah wajib untuk OO.

Bill K.
sumber
Jawaban ini sangat mengagumkan. Saya memiliki masalah ini (jelas), membaca jawaban ini dengan agak ragu, kembali dan melihat kode saya dan bertanya "objek apa yang hilang yang seharusnya memiliki metode ini". Lihatlah, solusi elegan ditemukan dan diisi objek-model-gap.
GreenAsJade
@Dupuplikator Dalam 40 tahun pemrograman saya jarang (tidak pernah?) Melihat kasus di mana saya diblokir oleh terlalu banyak tingkat tipuan (selain dari pertarungan sesekali dengan IDE saya untuk melompat ke implementasi daripada antarmuka) tapi saya ' Sudah sering (Sangat Sering) melihat kasus di mana orang menulis kode mengerikan daripada membuat kelas yang jelas dibutuhkan. Meskipun saya percaya terlalu banyak tipuan mungkin telah menggigit beberapa orang, saya akan keliru di samping prinsip-prinsip OO yang tepat seperti setiap kelas melakukan satu hal dengan baik, penahanan yang tepat, dll.
Bill K
@ BillK Umumnya, ide yang bagus. Tetapi tanpa pintu keluar, enkapsulasi dapat benar-benar menghalangi. Dan ada keindahan tertentu untuk algoritma gratis yang dapat Anda cocokkan dengan jenis apa pun yang Anda inginkan, meskipun diakui dalam bahasa OO ketat apa pun tetapi OO cukup canggung.
Deduplicator
@Dupuplikator Saat menulis Fungsi 'utilitas' yang harus ditulis di luar persyaratan bisnis tertentu, Anda benar. Kelas utilitas penuh fungsi (seperti koleksi) hanya canggung di OO karena mereka tidak terkait dengan data. ini adalah "Escape Hatch" yang digunakan orang-orang yang merasa tidak nyaman dengan penggunaan OO (juga, vendor alat harus menggunakannya untuk fungsi yang sangat umum). Saran saya di atas adalah untuk membungkus peretasan ini di kelas ketika Anda mengerjakan logika bisnis sehingga Anda dapat mengaitkan kembali kode Anda dengan data Anda.
Bill K
1

Anda bisa menggunakan pola Provider dan menyuntikkan Anda FooSomethingdengan FooUtilProviderobjek (bisa dalam konstruktor; hanya sekali).

FooUtilProviderhanya akan memiliki satu metode: FooUtils get();. Kelas kemudian akan menggunakan FooUtilsinstance apa pun yang disediakannya.

Nah, itu satu langkah lagi dari menggunakan kerangka kerja Dependency Injection, saat Anda hanya perlu memasang cointainer DI dua kali: untuk kode produksi, dan untuk suite pengujian Anda. Anda hanya mengikat FooUtilsantarmuka dengan salah satu RealFooUtilsatau MockedFooUtils, dan sisanya terjadi secara otomatis. Setiap objek yang bergantung pada FooUtilsProvidermendapatkan versi yang tepat FooUtils.

Konrad Morawski
sumber