it-swarm-id.com

Apakah objek konfigurasi tunggal adalah ide yang buruk?

Pada sebagian besar aplikasi saya, saya memiliki objek "config" tunggal atau statis, yang bertugas membaca berbagai pengaturan dari disk. Hampir semua kelas menggunakannya, untuk berbagai keperluan. Pada dasarnya itu hanya tabel hash dari pasangan nama/nilai. Ini hanya baca, jadi saya belum terlalu peduli dengan kenyataan bahwa saya memiliki begitu banyak negara global. Tapi sekarang saya mulai dengan pengujian unit, itu mulai menjadi masalah.

Satu masalah adalah Anda biasanya tidak ingin menguji dengan konfigurasi yang sama dengan yang Anda jalankan. Ada beberapa solusi untuk ini:

  • Berikan objek konfigurasi setter yang HANYA digunakan untuk pengujian, sehingga Anda dapat lulus dalam pengaturan yang berbeda.
  • Lanjutkan menggunakan objek konfigurasi tunggal, tetapi ubahlah dari satu singleton ke instance yang Anda berikan di mana-mana yang diperlukan. Kemudian Anda dapat membangunnya sekali dalam aplikasi Anda, dan sekali dalam pengujian Anda, dengan pengaturan yang berbeda.

Namun demikian, Anda masih memiliki masalah kedua: hampir semua kelas dapat menggunakan objek konfigurasi. Jadi dalam sebuah tes, Anda perlu mengatur konfigurasi untuk kelas yang diuji, tetapi juga SEMUA dependensinya. Ini dapat membuat kode pengujian Anda jelek.

Saya mulai sampai pada kesimpulan bahwa objek konfigurasi semacam ini adalah ide yang buruk. Bagaimana menurut anda? Apa saja alternatifnya? Dan bagaimana Anda memulai refactoring aplikasi yang menggunakan konfigurasi di mana-mana?

45
JW01

Saya tidak punya masalah dengan objek konfigurasi tunggal, dan dapat melihat keuntungan dalam menjaga semua pengaturan di satu tempat.

Namun, menggunakan objek tunggal di mana-mana akan menghasilkan kopling tingkat tinggi antara objek config dan kelas yang menggunakannya. Jika Anda perlu mengubah kelas config, Anda mungkin harus mengunjungi setiap instance yang digunakan dan memeriksa apakah Anda tidak merusak apa pun.

Salah satu cara untuk mengatasinya adalah dengan membuat banyak antarmuka yang mengekspos bagian-bagian dari objek konfigurasi yang diperlukan oleh berbagai bagian aplikasi. Daripada mengizinkan kelas-kelas lain untuk mengakses objek konfigurasi, Anda memberikan contoh antarmuka ke kelas yang membutuhkannya. Dengan begitu, bagian-bagian aplikasi yang menggunakan konfigurasi bergantung pada kumpulan bidang yang lebih kecil di antarmuka daripada seluruh kelas konfigurasi. Akhirnya, untuk pengujian unit Anda dapat membuat kelas khusus yang mengimplementasikan antarmuka.

Jika Anda ingin menjelajahi ide-ide ini lebih lanjut, saya sarankan membaca tentang SOLID prinsip , terutama Prinsip Segregasi Antarmuka dan Prinsip Ketergantungan Pembalikan .

39
Kramii

Saya memisahkan kelompok pengaturan terkait dengan antarmuka.

Sesuatu seperti:

public interface INotificationEmailSettings {
   public string To { get; set; }
}

public interface IMediaFileSettings {
    public string BasePath { get; set; }
}

dll.

Sekarang, untuk lingkungan nyata, satu kelas akan mengimplementasikan banyak antarmuka ini. Kelas itu mungkin berasal dari DB atau konfigurasi aplikasi atau apa pun yang Anda miliki, tetapi sering kali ia tahu cara mendapatkan sebagian besar pengaturan.

Tetapi pemisahan dengan antarmuka membuat dependensi aktual jauh lebih eksplisit dan berbutir halus, dan ini merupakan peningkatan yang jelas untuk testabilitas.

Tapi .. Saya tidak selalu ingin dipaksa untuk menyuntikkan atau memberikan pengaturan sebagai ketergantungan. Ada argumen yang dapat dibuat bahwa saya harus, untuk konsistensi atau kesaksian. Namun dalam kehidupan nyata sepertinya pembatasan yang tidak perlu.

Jadi, saya akan menggunakan kelas statis sebagai fasad, menyediakan entri yang mudah dari mana saja ke pengaturan, dan dalam kelas statis itu saya akan mencari layanan implementasi antarmuka dan mendapatkan pengaturan.

Saya tahu lokasi layanan mendapatkan jempol cepat dari sebagian besar, tapi mari kita hadapi itu, memberikan ketergantungan melalui konstruktor adalah berat, dan kadang-kadang berat itu lebih dari yang saya mau tanggung. Layanan Lokasi adalah solusi untuk mempertahankan testabilitas melalui pemrograman ke antarmuka dan memungkinkan beberapa implementasi sambil tetap memberikan kemudahan (dalam beberapa situasi yang diukur dengan tepat dan tepat) dari titik masuk tunggal statis.

public static class AllSettings {
    public INotificationEmailSettings NotificationEmailSettings {
        get {
            return ServiceLocator.Get<INotificationEmailSettings>();
        }
    }
}

Saya menemukan campuran ini menjadi yang terbaik dari semua dunia.

8
quentin-starin

Ya, seperti yang Anda sadari, objek konfigurasi global membuat pengujian unit menjadi sulit. Memiliki setter "rahasia" untuk pengujian unit adalah peretasan cepat, yang walaupun tidak bagus, bisa sangat berguna: ini memungkinkan Anda untuk mulai menulis tes unit, sehingga Anda dapat memperbaiki kode Anda ke arah desain yang lebih baik dari waktu ke waktu.

(Referensi wajib: Bekerja Secara Efektif dengan Kode Legacy . Ini berisi ini dan banyak lagi trik yang tak ternilai untuk unit yang menguji kode lama.)

Dalam pengalaman saya, yang terbaik adalah memiliki ketergantungan sesedikit mungkin pada konfigurasi global. Yang belum tentu nol - itu semua tergantung pada keadaan. Memiliki beberapa kelas "penyelenggara" tingkat tinggi yang mengakses konfigurasi global dan meneruskan properti konfigurasi aktual ke objek yang mereka buat dan gunakan dapat bekerja dengan baik, selama penyelenggara hanya melakukan itu, mis. tidak mengandung banyak kode yang dapat diuji sendiri. Ini memungkinkan Anda untuk menguji sebagian besar atau semua fungsi penting unit yang dapat diuji dari aplikasi, sementara masih tidak sepenuhnya mengganggu skema konfigurasi fisik yang ada.

Ini sudah mendekati solusi injeksi ketergantungan asli, seperti Spring for Java. Jika Anda dapat bermigrasi ke kerangka kerja seperti itu, baik. Tetapi dalam kehidupan nyata, dan terutama ketika berhadapan dengan aplikasi warisan, seringkali refactoring yang lambat dan teliti terhadap DI adalah kompromi terbaik yang dapat dicapai.

4
Péter Török

Dalam pengalaman saya, di dunia Java dunia, inilah yang dimaksudkan dengan Spring. Ia menciptakan dan mengelola objek (kacang-kacangan) berdasarkan properti tertentu yang diset pada saat runtime, dan sebaliknya transparan untuk Anda Anda dapat menggunakan file test.config yang disebutkan oleh Anon atau Anda dapat memasukkan beberapa logika dalam file konfigurasi yang akan ditangani Spring untuk menyetel properti berdasarkan beberapa kunci lain (mis. nama host atau lingkungan).

Mengenai masalah kedua Anda, Anda dapat menyiasatinya dengan beberapa arsitektur ulang tetapi tidak separah kelihatannya. Apa artinya ini dalam kasus Anda adalah bahwa Anda tidak akan, misalnya, memiliki objek global Config yang digunakan berbagai kelas lainnya. Anda baru saja mengkonfigurasi kelas-kelas lain melalui Spring dan kemudian tidak memiliki objek config karena semua konfigurasi yang diperlukan mendapatkannya melalui Spring, sehingga Anda dapat menggunakan kelas-kelas tersebut secara langsung dalam kode Anda.

2
Justin Beal

Pertanyaan ini sebenarnya tentang masalah yang lebih umum daripada konfigurasi. Segera setelah saya membaca "singleton" Word, saya langsung memikirkan semua masalah yang terkait dengan pola itu, yang paling tidak adalah kemampuan pengujian yang buruk.

Pola Singleton "dianggap berbahaya". Artinya, itu bukan selal hal yang salah, tapi biasanya adalah. Jika Anda pernah mempertimbangkan untuk menggunakan pola tunggal untuk apa saja, berhentilah untuk mempertimbangkan:

  • Apakah Anda perlu mensubklasifikasikannya?
  • Apakah Anda perlu memprogram ke antarmuka?
  • Apakah Anda perlu menjalankan unit test melawannya?
  • Apakah Anda perlu sering memodifikasinya?
  • Apakah aplikasi Anda dimaksudkan untuk mendukung berbagai platform/lingkungan produksi?
  • Apakah Anda sedikit khawatir tentang penggunaan memori?

Jika jawaban Anda adalah "ya" untuk semua ini (dan mungkin beberapa hal lain yang saya tidak pikirkan), maka Anda mungkin tidak ingin menggunakan singleton. Konfigurasi seringkali membutuhkan fleksibilitas yang jauh lebih besar daripada yang dapat diberikan oleh singleton (atau dalam hal ini, kelas instanceless).

Jika Anda ingin hampir semua manfaat dari singleton tanpa rasa sakit, maka gunakan kerangka injeksi ketergantungan seperti Spring atau Castle atau apa pun yang tersedia untuk lingkungan Anda. Dengan begitu Anda hanya perlu mendeklarasikannya sekali dan wadah itu akan secara otomatis memberikan contoh untuk apa pun yang membutuhkannya.

2
Aaronaught

Salah satu cara saya menangani ini dalam C # adalah memiliki objek tunggal yang mengunci selama pembuatan instance dan menginisialisasi semua data sekaligus untuk digunakan oleh semua objek klien. Jika singleton ini dapat menangani pasangan kunci/nilai, Anda dapat menyimpan segala macam data termasuk kunci kompleks untuk digunakan oleh banyak klien dan jenis klien yang berbeda. Jika Anda ingin tetap dinamis dan memuat data klien baru sesuai kebutuhan, Anda dapat memverifikasi keberadaan kunci utama klien dan, jika tidak ada, muat data untuk klien itu, tambahkan ke kamus utama. Mungkin juga konfigurasi singleton primer dapat berisi set data klien di mana banyak tipe klien yang sama menggunakan data yang sama yang diakses melalui konfigurasi primer singleton. Sebagian besar organisasi objek konfigurasi ini mungkin bergantung pada bagaimana klien konfigurasi Anda perlu mengakses informasi ini dan apakah informasi itu statis atau dinamis. Saya telah menggunakan konfigurasi kunci/nilai serta API objek tertentu bergantung pada yang diperlukan.

Salah satu lajang konfigurasi saya dapat menerima pesan untuk dimuat ulang dari basis data. Dalam hal ini, saya memuat koleksi objek kedua dan hanya mengunci koleksi utama untuk menukar koleksi. Ini mencegah pemblokiran bacaan pada utas lainnya.

Jika memuat dari file konfigurasi, Anda bisa memiliki hierarki file. Pengaturan dalam satu file dapat menentukan file mana yang akan dimuat. Saya telah menggunakan mekanisme ini dengan layanan C # Windows yang memiliki banyak komponen opsional, masing-masing dengan pengaturan konfigurasi mereka sendiri. Pola struktur file adalah sama di seluruh file, tetapi dimuat atau tidak berdasarkan pada pengaturan file utama.

0
Tim Butterfield

Kelas yang Anda gambarkan terdengar seperti God object antipattern kecuali dengan data alih-alih fungsi. Itu masalah. Anda mungkin harus membuat data konfigurasi dibaca dan disimpan di objek yang sesuai sambil hanya membaca data lagi jika diperlukan karena alasan tertentu.

Selain itu Anda menggunakan Singleton karena alasan yang tidak tepat. Singleton hanya boleh digunakan ketika keberadaan beberapa objek akan membuat keadaan buruk. Menggunakan Singleton dalam kasus ini tidak tepat karena memiliki lebih dari satu pembaca konfigurasi tidak boleh menyebabkan status kesalahan segera. Jika Anda memiliki lebih dari satu, Anda mungkin melakukan kesalahan, tetapi tidak mutlak bahwa Anda hanya memiliki satu dari pembaca konfigurasi.

Akhirnya, menciptakan negara global semacam itu merupakan pelanggaran enkapsulasi karena Anda mengizinkan lebih banyak kelas daripada yang perlu diketahui mengakses data secara langsung.

0
indyK1ng

Saya pikir jika ada banyak pengaturan, dengan "rumpun" yang terkait, masuk akal untuk membagi mereka menjadi antarmuka yang terpisah dengan implementasi masing-masing - kemudian menyuntikkan antarmuka tersebut di mana diperlukan dengan DI. Anda mendapatkan testabilitas, kopling lebih rendah, dan SRP.

Saya terinspirasi oleh masalah ini oleh Joshua Flanagan dari Los Techies; dia menulis artikel tentang masalah ini beberapa waktu lalu.

0
Grant Palin