seberapa rumit konstruktor seharusnya

18

Saya sedang berdiskusi dengan rekan kerja saya tentang seberapa banyak pekerjaan yang dapat dilakukan oleh seorang konstruktor. Saya memiliki kelas, B yang secara internal membutuhkan objek lain A. Objek A adalah salah satu dari sedikit anggota yang perlu dilakukan oleh kelas B. Semua metode publiknya tergantung pada objek internal A. Informasi tentang objek A disimpan dalam DB, jadi saya mencoba memvalidasi dan mendapatkannya dengan mencarinya di DB di konstruktor. Rekan kerja saya menunjukkan bahwa konstruktor seharusnya tidak melakukan banyak pekerjaan selain menangkap parameter konstruktor. Karena semua metode publik akan gagal juga jika objek A tidak ditemukan menggunakan input ke konstruktor, saya berpendapat bahwa alih-alih membiarkan contoh dibuat dan kemudian gagal, sebenarnya lebih baik untuk membuang di awal konstruktor.

Apa yang dipikirkan orang lain? Saya menggunakan C # jika itu membuat perbedaan.

Membaca Apakah pernah ada alasan untuk melakukan semua pekerjaan objek dalam sebuah konstruktor? saya bertanya-tanya mengambil objek A dengan pergi ke DB adalah bagian dari "inisialisasi lain yang diperlukan untuk membuat objek siap digunakan" karena jika pengguna memberikan nilai yang salah kepada konstruktor, saya tidak akan dapat menggunakan metode publiknya.

Konstruktor harus instantiate bidang objek dan melakukan inisialisasi lain yang diperlukan untuk membuat objek siap digunakan. Ini umumnya berarti konstruktor kecil, tetapi ada skenario di mana ini akan menjadi sejumlah besar pekerjaan.

Taein Kim
sumber
8
Saya tidak setuju. Melihat pada prinsip inversi dependensi, akan lebih baik jika objek A diserahkan ke objek B dalam keadaan valid di konstruktornya.
Jimmy Hoffa
5
Apakah Anda bertanya berapa banyak yang bisa dilakukan? Berapa banyak yang harus dilakukan? Atau berapa banyak situasi spesifik Anda yang harus dilakukan? Itu tiga pertanyaan yang sangat berbeda.
Bobson
1
Saya setuju dengan saran Jimmy Hoffa untuk melihat injeksi ketergantungan (atau "inversi ketergantungan"). Itulah pemikiran pertama saya membaca deskripsi, karena sepertinya Anda sudah setengah jalan ke sana; Anda sudah memiliki fungsionalitas yang dipisahkan menjadi kelas A, yang digunakan kelas B. Saya tidak dapat berbicara dengan kasus Anda, tetapi saya biasanya menemukan bahwa kode refactoring untuk menggunakan pola injeksi ketergantungan membuat kelas lebih sederhana / kurang terjalin.
Apprentice Dr. Wily
1
Bobson, saya mengubah judul menjadi 'harus'. Saya meminta praktik terbaik di sini.
Taein Kim
1
@JimmyHoffa: mungkin Anda harus memperluas komentar Anda menjadi sebuah jawaban.
kevin cline

Jawaban:

22

Pertanyaan Anda terdiri dari dua bagian yang sepenuhnya terpisah:

Haruskah saya membuang pengecualian dari konstruktor, atau haruskah saya gagal metode?

Ini jelas penerapan prinsip Gagal-cepat . Membuat konstruktor gagal jauh lebih mudah untuk di-debug dibandingkan harus menemukan mengapa metode ini gagal. Misalnya, Anda mungkin mendapatkan instance sudah dibuat dari beberapa bagian lain dari kode dan Anda mendapatkan kesalahan saat memanggil metode. Apakah sudah jelas objek yang dibuat salah? Tidak.

Adapun masalah "bungkus panggilan di coba / tangkap". Pengecualian dimaksudkan untuk menjadi luar biasa . Jika Anda tahu beberapa kode akan melempar pengecualian, Anda tidak membungkus kode dalam try / catch, tetapi Anda memvalidasi parameter kode itu sebelum Anda mengeksekusi kode yang dapat melempar pengecualian. Pengecualian hanya dimaksudkan sebagai cara untuk memastikan sistem tidak masuk ke keadaan tidak valid. Jika Anda tahu beberapa parameter input dapat menyebabkan keadaan tidak valid, Anda memastikan parameter itu tidak pernah terjadi. Dengan cara ini, Anda hanya perlu mencoba / menangkap di tempat, di mana Anda dapat secara logis menangani pengecualian, yang biasanya pada batas sistem.

Dapatkah saya mengakses "bagian lain dari sistem, seperti DB" dari konstruktor.

Saya pikir ini bertentangan dengan prinsip paling tidak heran . Tidak banyak orang mengharapkan konstruktor mengakses DB. Jadi tidak, Anda tidak harus melakukan itu.

Euforia
sumber
2
Solusi yang lebih tepat untuk ini biasanya adalah menambahkan metode tipe "terbuka" di mana konstruksi gratis, tetapi tidak dapat digunakan sebelum Anda "membuka" / "menghubungkan" / etc yang mana semua inisialisasi aktual terjadi. Konstruktor gagal dapat menyebabkan segala macam masalah ketika Anda di masa depan ingin melakukan konstruksi parsial objek yang merupakan kemampuan yang berguna.
Jimmy Hoffa
@ JimmyHoffa Saya tidak melihat perbedaan antara metode konstruktor dan metode spesifik untuk konstruksi.
Euforia
4
Anda mungkin tidak, tetapi banyak yang melakukannya. Pikirkan ketika Anda membuat objek koneksi, apakah konstruktor menghubungkannya? Apakah objek dapat digunakan jika tidak terhubung? Dalam kedua pertanyaan, jawabannya adalah tidak, karena membantu memiliki objek koneksi yang dibangun sebagian sehingga Anda dapat mengubah pengaturan dan hal-hal sebelum Anda membuka koneksi. Ini adalah pendekatan umum untuk skenario ini. Saya masih berpikir injeksi konstruktor adalah pendekatan yang tepat untuk skenario di mana objek A memiliki ketergantungan sumber daya tetapi metode terbuka masih lebih baik daripada meminta konstruktor melakukan pekerjaan berbahaya.
Jimmy Hoffa
@JimmyHoffa Tapi itu adalah persyaratan khusus kelas koneksi, bukan aturan umum desain OOP. Saya benar-benar akan menyebutnya kasus luar biasa, daripada aturan. Anda pada dasarnya berbicara tentang pola pembangun.
Euforia
2
Itu bukan keharusan, itu adalah keputusan desain. Mereka bisa membuat konstruktor mengambil string koneksi dan segera terhubung ketika dibangun. Mereka memutuskan tidak terlalu karena akan sangat membantu untuk dapat membuat objek, kemudian men-tweak mencari koneksi string atau bit dan bobbles lainnya dan menghubungkannya nanti ,
Jimmy Hoffa
19

Ugh.

Konstruktor harus melakukan sesedikit mungkin - masalahnya adalah bahwa perilaku pengecualian pada konstruktor canggung. Sangat sedikit programmer yang tahu perilaku yang tepat (termasuk skenario pewarisan), dan memaksa pengguna Anda untuk mencoba / menangkap setiap instantiasi adalah ... menyakitkan paling-paling.

Ada dua bagian untuk ini, di konstruktor dan menggunakan konstruktor. Itu canggung di konstruktor karena Anda tidak bisa berbuat banyak di sana ketika pengecualian terjadi. Anda tidak dapat mengembalikan jenis yang berbeda. Anda tidak dapat mengembalikan nol. Anda pada dasarnya dapat memikirkan kembali pengecualian, mengembalikan objek yang rusak (konstruktor buruk), atau (kasus terbaik) mengganti beberapa bagian internal dengan default waras. Menggunakan konstruktor canggung karena Instansiasi Anda (dan Instansiasi dari semua tipe turunan!) Dapat melempar. Maka Anda tidak dapat menggunakannya dalam inisialisasi anggota. Anda akhirnya menggunakan pengecualian untuk logika untuk melihat "apakah kreasi saya berhasil?", Melampaui keterbacaan (dan kerapuhan) yang disebabkan oleh coba / tangkap di mana-mana.

Jika konstruktor Anda melakukan hal-hal non-sepele, atau hal-hal yang dapat diperkirakan gagal, pertimbangkan Createmetode statis (dengan konstruktor non-publik). Ini jauh lebih bermanfaat, lebih mudah untuk debug, dan masih membuat Anda contoh yang dibangun sepenuhnya ketika berhasil.

Telastyn
sumber
2
Skenario konstruksi yang rumit persis mengapa ada pola kreasi seperti yang Anda sebutkan di sini. Ini adalah pendekatan yang baik untuk konstruksi bermasalah ini. Saya diberikan untuk berpikir tentang bagaimana. NET Connectionobjek dapat CreateCommanduntuk Anda sehingga Anda tahu perintah terhubung dengan tepat.
Jimmy Hoffa
2
Untuk ini saya akan menambahkan bahwa Database adalah ketergantungan eksternal yang berat, misalkan Anda ingin beralih database di beberapa titik di masa depan (atau mengejek objek untuk pengujian), memindahkan kode semacam ini ke kelas Pabrik (atau sesuatu seperti layanan IService penyedia) tampaknya seperti pendekatan yang lebih bersih
Jason Sperske
4
dapatkah Anda menguraikan "perilaku pengecualian dalam konstruktor canggung"? Jika Anda menggunakan Buat, tidakkah Anda perlu membungkusnya dalam mencoba menangkap seperti Anda akan membungkus panggilan ke konstruktor? Saya merasa seperti Buat hanyalah nama yang berbeda untuk konstruktor. Mungkin saya tidak mendapatkan jawaban Anda benar?
Taein Kim
1
Harus mencoba menangkap pengecualian yang muncul dari konstruktor, +1 menjadi argumen yang menentang ini. Setelah bekerja dengan orang-orang yang mengatakan "Tidak ada pekerjaan di konstruktor", ini juga dapat membuat beberapa pola aneh. Saya telah melihat kode di mana setiap objek tidak hanya memiliki konstruktor, tetapi juga beberapa bentuk Inisialisasi () di mana, coba tebak? Objek tidak benar-benar valid sampai itu disebut baik. Dan kemudian Anda memiliki dua hal untuk dipanggil: ctor dan Initialize (). Masalah melakukan pekerjaan untuk mengatur objek tidak hilang - C # dapat menghasilkan solusi yang berbeda dari katakanlah C ++. Pabrik bagus!
J Trana
1
@ jtrana - yeah, inisialisasi tidak baik. Konstruktor menginisialisasi ke beberapa standar waras atau pabrik atau membuat metode membangunnya dan mengembalikan contoh yang dapat digunakan.
Telastyn
1

Konstruktor - keseimbangan kompleksitas metode memang masalah diskusi tentang stile yang baik. Cukup sering Class() {}digunakan konstruktor kosong , dengan metode Init(..) {..}untuk hal-hal yang lebih rumit. Di antara argumen yang harus dipertimbangkan:

  • Kemungkinan serialisasi kelas
  • Dengan metode pemisahan Init dari konstruktor, Anda sering perlu mengulangi urutan yang sama Class x = new Class(); x.Init(..);berkali-kali, sebagian dalam Tes Unit. Namun, tidak begitu baik untuk membaca. Terkadang, saya menempatkan mereka dalam satu baris kode.
  • Ketika tujuan Tes Unit hanyalah uji inisialisasi Kelas, lebih baik memiliki Init sendiri, untuk melakukan tindakan yang lebih rumit dipenuhi satu-per-satu, dengan Assert intermediate.
Pavel Khrapkin
sumber
Keuntungannya, menurut saya, initmetode ini adalah bahwa menangani kegagalan jauh lebih mudah. Secara khusus, nilai pengembalian initmetode dapat menunjukkan apakah inisialisasi berhasil, dan metode dapat memastikan objek selalu dalam keadaan baik.
pqnet