Saya sedang mengerjakan desain, tetapi tetap saja memukul penghalang jalan. Saya memiliki kelas tertentu (ModelDef) yang pada dasarnya adalah pemilik pohon simpul kompleks yang dibangun dengan mem-parsing skema XML (pikirkan DOM). Saya ingin mengikuti prinsip-prinsip desain yang baik (SOLID), dan memastikan bahwa sistem yang dihasilkan mudah diuji. Saya memiliki setiap niat menggunakan DI untuk meneruskan dependensi ke dalam konstruktor ModelDef (sehingga ini dapat dengan mudah ditukar, jika perlu, selama pengujian).
Apa yang saya perjuangkan adalah penciptaan pohon simpul. Pohon ini akan terdiri dari objek "nilai" sederhana yang tidak perlu diuji secara independen. (Namun, saya masih bisa meneruskan Pabrik Abstrak ke ModelDef untuk membantu pembuatan benda-benda ini.)
Tetapi saya terus membaca bahwa seorang konstruktor tidak boleh melakukan pekerjaan nyata (mis. Cacat: Pembuat pekerjaan nyata ). Ini masuk akal bagi saya jika "pekerjaan nyata" berarti membangun objek yang sangat bergantung berat yang nantinya ingin dihilangkan untuk pengujian. (Itu harus diteruskan melalui DI.)
Tapi bagaimana dengan benda bernilai ringan seperti pohon simpul ini? Pohon itu harus dibuat di suatu tempat, kan? Mengapa tidak melalui konstruktor ModelDef (menggunakan, katakanlah, metode buildNodeTree ())?
Saya tidak benar-benar ingin membuat pohon simpul di luar ModelDef dan kemudian meneruskannya (melalui konstruktor DI), karena membuat pohon simpul dengan menguraikan skema memerlukan sejumlah besar kode kompleks - kode yang perlu diuji secara menyeluruh . Saya tidak ingin menurunkannya ke "lem" kode (yang seharusnya relatif sepele, dan kemungkinan tidak akan langsung diuji).
Saya telah berpikir untuk meletakkan kode untuk membuat simpul pohon di objek "builder" yang terpisah, tetapi ragu untuk menyebutnya "builder", karena itu tidak benar-benar cocok dengan Pola Builder (yang tampaknya lebih peduli dengan menghilangkan telescoping konstruktor). Tetapi bahkan jika saya menyebutnya sesuatu yang berbeda (misalnya NodeTreeConstructor), masih terasa seperti sedikit peretasan hanya untuk menghindari konstruktor ModelDef membangun pohon simpul. Itu harus dibangun di suatu tempat; mengapa tidak di objek yang akan memilikinya?
Jawaban:
Dan, selain apa yang disarankan Ross Patterson, pertimbangkan posisi ini yang merupakan kebalikannya:
Ambil pepatah seperti "Jangan Melakukan Pekerjaan Nyata di Konstruktor-Mu" dengan sebutir garam.
Konstruktor sebenarnya hanyalah metode statis. Jadi, secara struktural, sebenarnya tidak ada banyak perbedaan antara:
a) konstruktor sederhana dan banyak metode pabrik statis yang kompleks, dan
b) konstruktor sederhana dan banyak konstruktor yang lebih kompleks.
Sebagian besar dari sentimen negatif terhadap melakukan pekerjaan nyata dalam konstruktor berasal dari periode tertentu dari sejarah C ++ ketika ada perdebatan tentang apa tepatnya objek akan dibiarkan jika pengecualian dilemparkan dalam konstruktor, dan apakah destructor harus dipanggil dalam peristiwa semacam itu. Bagian dari sejarah C ++ telah berakhir, dan masalah telah diselesaikan, sementara dalam bahasa seperti Jawa tidak pernah ada masalah seperti ini sejak awal.
Pendapat saya adalah bahwa jika Anda hanya menghindari menggunakan
new
konstruktor, (seperti niat Anda untuk menggunakan Injeksi Ketergantungan,) Anda harus baik-baik saja. Saya menertawakan pernyataan seperti "logika kondisional atau looping dalam konstruktor adalah tanda peringatan cacat".Selain semua itu, secara pribadi, saya akan mengambil logika parsing XML dari konstruktor, bukan karena jahat memiliki logika kompleks dalam konstruktor, tetapi karena itu baik untuk mengikuti prinsip "pemisahan keprihatinan". Jadi, saya akan memindahkan logika parsing XML ke beberapa kelas yang terpisah sama sekali, bukan ke beberapa metode statis milik
ModelDef
kelas Anda .Amandemen
Saya kira bahwa jika Anda memiliki metode di luar
ModelDef
yang membuatModelDef
dari XML, Anda akan perlu membuat instantiate beberapa struktur data pohon sementara yang dinamis, mengisi itu dengan mem-parsing XML Anda, dan kemudian membuatModelDef
struktur baru Anda lewat itu sebagai parameter konstruktor. Jadi, itu mungkin bisa dianggap sebagai aplikasi dari pola "Builder". Ada analogi yang sangat dekat antara apa yang ingin Anda lakukan danString
&StringBuilder
pasangan. Namun, saya telah menemukan T&J ini yang tampaknya tidak setuju, karena alasan yang tidak jelas bagi saya: Stackoverflow - StringBuilder dan Builder Pattern . Jadi, untuk menghindari perdebatan panjang di sini tentang apakahStringBuilder
menerapkan atau tidak "membangun" pola, saya akan mengatakan merasa bebas untuk terinspirasi oleh bagaimanaStrungBuilder
bekerja dalam menghasilkan solusi yang sesuai dengan kebutuhan Anda, dan menundanya menyebutnya sebagai aplikasi dari pola "Builder" sampai detail kecil itu diselesaikan.Lihat pertanyaan baru ini: Programmer SE: Apakah "StringBuilder" aplikasi Pola Desain Builder?
sumber
Anda sudah memberikan alasan terbaik untuk tidak melakukan pekerjaan ini di
ModelDef
konstruktor:ModelDef
yang mengatakan itu hanya dapat dibuat dari dokumen XML.Kedengarannya seperti kelas Anda harus memiliki berbagai metode statis seperti
ModelDef.FromXmlString(string xmlDocument)
,ModelDef.FromXmlDoc(XmlDoc parsedNodeTree)
, dllsumber
public static ModelDef createFromXmlString( string xmlDocument )
. Ini adalah praktik yang cukup umum. Terkadang saya juga melakukannya. Saran saya agar Anda juga dapat melakukan konstruktor saja adalah tipe standar respons saya dalam situasi di mana saya menduga bahwa pendekatan alternatif dianggap "tidak halal" tanpa alasan yang kuat.Saya pernah mendengar "aturan" itu sebelumnya. Dalam pengalaman saya itu benar dan salah.
Dalam orientasi objek yang lebih "klasik" kita berbicara tentang objek yang merangkum keadaan dan perilaku. Dengan demikian konstruktor objek harus memastikan bahwa objek diinisialisasi ke keadaan yang valid (dan memberi sinyal kesalahan jika argumen yang diberikan tidak membuat objek valid). Memastikan bahwa suatu objek diinisialisasi ke keadaan yang valid tentu terdengar seperti pekerjaan nyata bagi saya. Dan ide ini memiliki kelebihan, jika Anda memiliki objek yang hanya memungkinkan inisialisasi ke status yang valid melalui konstruktor dan objek dengan benar merangkumnya sehingga setiap metode yang mengubah status juga memeriksa bahwa itu tidak mengubah keadaan menjadi sesuatu yang buruk. ... maka objek itu pada dasarnya menjamin bahwa itu "selalu valid". Itu adalah properti yang sangat bagus!
Jadi masalah umumnya datang ketika kita mencoba memecah semuanya menjadi beberapa bagian kecil untuk menguji dan mengejek barang. Karena jika suatu objek dienkapsulasi dengan benar maka Anda tidak dapat benar-benar masuk ke sana dan mengganti FooBarService dengan FooBarService yang diolok-olok dan Anda (mungkin) tidak bisa hanya mengubah nilai mau tak mau agar sesuai dengan pengujian Anda (atau, mungkin diperlukan lebih banyak kode daripada tugas sederhana).
Dengan demikian kita mendapatkan "aliran pemikiran" lainnya, yaitu SOLID. Dan dalam aliran pemikiran ini, kemungkinan besar lebih benar bahwa kita seharusnya tidak melakukan pekerjaan nyata dalam konstruktor. Kode SOLID seringkali (tetapi tidak selalu) lebih mudah untuk diuji. Tetapi juga bisa lebih sulit untuk dipikirkan. Kami memecah kode kami menjadi objek kecil dengan satu tanggung jawab, dan dengan demikian sebagian besar objek kami tidak lagi merangkum keadaan mereka (dan umumnya berisi keadaan atau perilaku). Kode validasi umumnya diekstraksi ke dalam kelas validator dan disimpan terpisah dari negara. Tetapi sekarang kita telah kehilangan kohesi, kita tidak bisa lagi memastikan bahwa objek kita valid ketika kita mendapatkannya dan sepenuhnyaTentu saja kita harus selalu memvalidasi bahwa prasyarat yang kita pikir miliki tentang objek itu benar sebelum kita mencoba melakukan sesuatu dengan objek tersebut. (Tentu saja, secara umum Anda melakukan validasi dalam satu lapisan dan kemudian menganggap bahwa objek tersebut valid di lapisan bawah.) Tetapi lebih mudah untuk menguji!
Jadi siapa yang benar?
Tidak ada yang benar-benar. Kedua aliran pemikiran memiliki kelebihan. Saat ini SOLID adalah semua kemarahan dan semua orang berbicara tentang SRP dan Open / Closed dan semua jazz itu. Tetapi hanya karena sesuatu itu populer tidak berarti bahwa itu adalah pilihan desain yang benar untuk setiap aplikasi. Jadi itu tergantung. Jika Anda bekerja di basis kode yang sangat mengikuti prinsip-prinsip SOLID maka ya, kerja nyata di konstruktor mungkin adalah ide yang buruk. Tetapi sebaliknya, lihat situasinya dan cobalah untuk menggunakan penilaian Anda. Sifat-sifat apa objek Anda keuntungan dari melakukan pekerjaan di constructor, apa sifat apakah itu kehilangan ? Seberapa cocokkah dengan keseluruhan arsitektur aplikasi Anda?
Pekerjaan nyata di konstruktor bukan antiputern, itu bisa sangat berlawanan bila digunakan di tempat yang benar. Tetapi harus didokumentasikan dengan jelas (bersama dengan pengecualian yang dapat dilemparkan, jika ada) dan seperti halnya dengan keputusan desain - harus sesuai dengan gaya umum yang digunakan dalam basis kode saat ini.
sumber
Ada masalah mendasar dengan aturan ini dan ini, apa yang merupakan "pekerjaan nyata"?
Anda dapat melihat dari artikel asli yang diposting dalam pertanyaan bahwa penulis mencoba untuk mendefinisikan apa "pekerjaan nyata" itu, tetapi sangat cacat. Agar suatu praktik menjadi baik, ia perlu prinsip yang jelas. Maksud saya, sehubungan dengan rekayasa perangkat lunak, gagasan tersebut harus portabel (agnostik untuk bahasa apa pun), diuji, dan terbukti. Sebagian besar dari apa yang diperdebatkan dalam artikel itu tidak sesuai dengan kriteria pertama itu. Berikut adalah beberapa indikator yang penulis sebutkan dalam artikel tentang apa yang merupakan "pekerjaan nyata" dan mengapa mereka bukan definisi yang buruk.
Penggunaan
new
kata kunci . Definisi itu secara mendasar cacat karena spesifik domain. Beberapa bahasa tidak menggunakannew
kata kunci. Pada akhirnya apa yang dia isyaratkan adalah bahwa itu seharusnya tidak membangun objek lain. Namun dalam banyak bahasa bahkan nilai yang paling dasar adalah objek itu sendiri. Jadi nilai apa pun yang diberikan dalam konstruktor juga membangun objek baru. Itu membuat ide ini terbatas pada bahasa-bahasa tertentu, dan indikator yang buruk untuk apa yang merupakan "pekerjaan nyata".Objek tidak sepenuhnya diinisialisasi setelah konstruktor selesai . Ini adalah aturan yang baik, tetapi juga bertentangan dengan beberapa aturan lain yang disebutkan dalam artikel itu. Sebuah contoh yang baik tentang bagaimana hal itu dapat bertentangan dengan yang lain disebutkan pertanyaan yang membawa saya ke sini. Dalam pertanyaan itu seseorang khawatir tentang menggunakan
sort
metode dalam konstruktor dalam apa yang tampaknya JavaScript karena prinsip ini. Dalam contoh ini orang tersebut membuat objek yang mewakili daftar objek lain yang diurutkan. Untuk tujuan diskusi, bayangkan bahwa kami memiliki daftar objek yang tidak disortir dan kami membutuhkan objek baru untuk mewakili daftar yang diurutkan. Kami membutuhkan objek baru ini karena beberapa bagian dari perangkat lunak kami mengharapkan daftar yang diurutkan, dan mari kita sebut objek iniSortedList
. Objek baru ini menerima daftar yang tidak disortir dan objek yang dihasilkan harus mewakili daftar objek yang sekarang diurutkan. Jika kita mengikuti aturan lain yang disebutkan dalam dokumen itu, yaitu tidak ada metode panggilan statis, tidak ada struktur aliran kontrol, tidak lebih dari penugasan, maka objek yang dihasilkan tidak akan dibangun dalam keadaan valid melanggar aturan lain yang sepenuhnya diinisialisasi setelah konstruktor selesai. Untuk memperbaikinya kita perlu melakukan beberapa pekerjaan dasar untuk membuat daftar yang tidak disortir diurutkan dalam konstruktor. Melakukan ini akan melanggar 3 aturan lainnya, membuat aturan lain tidak relevan.Pada akhirnya, aturan untuk tidak melakukan "pekerjaan nyata" dalam sebuah konstruktor ini tidak jelas dan cacat. Mencoba mendefinisikan apa "pekerjaan nyata" adalah latihan yang sia-sia. Aturan terbaik dalam artikel itu adalah bahwa ketika konstruktor selesai harus sepenuhnya diinisialisasi. Ada sejumlah praktik terbaik lainnya yang akan membatasi seberapa banyak pekerjaan yang dilakukan dalam konstruktor. Sebagian besar dari ini dapat diringkas dalam prinsip-prinsip SOLID, dan prinsip-prinsip yang sama tidak akan mencegah Anda melakukan pekerjaan di konstruktor.
PS. Saya merasa wajib untuk mengatakan bahwa sementara saya menyatakan di sini bahwa tidak ada yang salah dengan melakukan beberapa pekerjaan di konstruktor, itu bukan tempat untuk melakukan banyak pekerjaan juga. SRP akan menyarankan bahwa seorang konstruktor harus melakukan pekerjaan yang cukup untuk membuatnya valid. Jika konstruktor Anda memiliki terlalu banyak baris kode (sangat subyektif yang saya tahu) maka mungkin melanggar prinsip ini, dan mungkin dapat dipecah menjadi metode dan objek yang lebih kecil dan lebih baik.
sumber