Saya telah melihat beberapa jawaban dan mencari di Google, tetapi saya tidak dapat menemukan sesuatu yang membantu (yaitu, itu tidak akan memiliki efek samping yang canggung).
Masalah saya, secara abstrak, adalah bahwa saya memiliki objek dan perlu melakukan urutan operasi yang panjang di atasnya; Saya menganggapnya sebagai semacam jalur perakitan, seperti membangun mobil.
Saya percaya benda-benda ini akan disebut Object Methods .
Jadi, dalam contoh ini di beberapa titik saya akan memiliki CarWithoutUpholstery di mana saya kemudian perlu menjalankan installBackSeat, installFrontSeat, installWoodenInserts (operasi tidak saling mengganggu, dan bahkan mungkin dilakukan secara paralel). Operasi ini dilakukan oleh CarWithoutUpholstery.worker () dan menghasilkan objek baru yang akan menjadi CarWithUpholstery, di mana saya akan menjalankan mungkin cleanInsides (), verifikasi No NolUpholsteryDefects (), dan sebagainya.
Operasi dalam satu fase sudah independen, yaitu, saya sudah bergulat dengan mereka yang dapat dieksekusi dalam urutan apa pun (kursi depan dan belakang dapat dipasang dalam urutan apa pun).
Logika saya saat ini menggunakan Refleksi untuk kesederhanaan implementasi.
Yaitu, begitu saya memiliki CarWithoutUpholstery, objek memeriksa dirinya sendiri untuk metode yang disebut performSomething (). Pada saat itu ia menjalankan semua metode ini:
myObject.perform001SomeOperation();
myObject.perform002SomeOtherOperation();
...
saat memeriksa kesalahan dan hal-hal lainnya. Sementara urutan operasi adalah penting, saya telah diberi urutan leksikografis dalam kasus saya pernah menemukan beberapa rangka penting setelah semua. Ini bertentangan dengan YAGNI , tetapi biayanya sangat kecil - semacam sederhana () - dan itu bisa menghemat penggantian nama metode besar-besaran (atau memperkenalkan beberapa metode lain untuk melakukan tes, misalnya berbagai metode) di telepon.
Contoh berbeda
Mari kita katakan bahwa alih-alih membangun mobil saya harus menyusun laporan Polisi Rahasia pada seseorang, dan menyerahkannya kepada Tuan Jahat saya . Objek terakhir saya adalah ReadyReport. Untuk membangunnya saya mulai dengan mengumpulkan informasi dasar (nama, nama keluarga, pasangan ...). Ini adalah Fase A. Saya, tergantung apakah ada pasangan atau tidak, saya mungkin harus melanjutkan ke fase B1 atau B2, dan mengumpulkan data seksualitas pada satu atau dua orang. Ini dibuat dari beberapa pertanyaan yang berbeda untuk Minion Jahat yang berbeda mengendalikan kehidupan malam, kamera jalanan, kwitansi penjualan toko seks dan apa yang tidak. Dan seterusnya dan seterusnya.
Jika korban tidak memiliki keluarga, saya bahkan tidak akan memasuki fase GetInformationAboutFamily, tetapi jika saya lakukan, maka tidak relevan apakah saya pertama-tama menargetkan ayah atau ibu atau saudara kandung (jika ada). Tapi saya tidak bisa melakukan itu jika saya belum melakukan FamilyStatusCheck, yang karena itu termasuk dalam fase sebelumnya.
Semuanya bekerja dengan luar biasa ...
- jika saya perlu beberapa operasi tambahan saya hanya perlu menambahkan metode pribadi,
- jika operasi ini umum untuk beberapa fase saya bisa mewarisi dari superclass,
- operasi sederhana dan mandiri. Tidak ada nilai dari satu operasi yang pernah diperlukan oleh yang lain (operasi yang tidak dilakukan dalam fase yang berbeda),
- objek di telepon tidak perlu melakukan banyak tes karena mereka bahkan tidak bisa eksis jika objek pencipta mereka tidak memverifikasi kondisi tersebut di tempat pertama. Yaitu, ketika menempatkan sisipan di dasbor, membersihkan dasbor dan memverifikasi dasbor, saya tidak perlu memverifikasi bahwa sebenarnya ada dasbor .
- memungkinkan untuk pengujian mudah. Saya dapat dengan mudah mengejek objek parsial dan menjalankan metode apa pun di atasnya, dan semua operasi adalah kotak hitam deterministik.
...tapi...
Masalahnya muncul ketika saya menambahkan satu operasi terakhir di salah satu objek metode saya, yang menyebabkan modul keseluruhan melebihi indeks kompleksitas wajib ("kurang dari metode pribadi N").
Saya sudah membawa masalah ini ke atas dan menyarankan bahwa dalam kasus ini, kekayaan metode pribadi bukanlah tanda bencana. Kompleksitas adalah di sana, tapi itu ada karena operasi adalah kompleks, dan benar-benar itu tidak semua yang rumit - itu hanya panjang .
Dengan menggunakan contoh Evil Overlord, masalah saya adalah bahwa Evil Overlord (alias Dia Yang Tidak Akan Ditolak ) meminta semua informasi diet, Pelayan diet saya memberi tahu saya bahwa saya perlu menanyakan restoran, dapur, pedagang kaki lima, pedagang kaki lima tanpa izin, rumah kaca pemilik dll., dan Tuan Jahat (sub) - yang dikenal sebagai Dia yang Juga Tidak Akan Ditolak - mengeluh bahwa saya melakukan terlalu banyak permintaan dalam fase GetDietaryInformation.
Catatan : Saya sadar bahwa dari beberapa sudut pandang ini sama sekali bukan masalah (mengabaikan kemungkinan masalah kinerja, dll.). Semua yang terjadi adalah bahwa metrik tertentu tidak bahagia, dan ada pembenaran untuk itu.
Apa yang saya pikir bisa saya lakukan
Terlepas dari yang pertama, semua opsi ini bisa dilakukan dan, saya pikir, dapat dipertahankan.
- Saya telah memverifikasi bahwa saya bisa licik dan menyatakan setengah metode saya
protected
. Tapi saya akan mengeksploitasi kelemahan dalam prosedur pengujian, dan selain membenarkan diri sendiri ketika tertangkap, saya tidak suka ini. Juga, ini adalah ukuran sementara. Bagaimana jika jumlah operasi yang diperlukan berlipat ganda? Tidak mungkin, tetapi bagaimana? - Saya dapat membagi fase ini secara sewenang-wenang menjadi AnnealedObjectAlpha, AnnealedObjectBravo dan AnnealedObjectCharlie, dan memiliki sepertiga operasi yang dilakukan pada setiap tahap. Saya mendapat kesan bahwa ini sebenarnya menambah kompleksitas (N-1 lebih banyak kelas), tanpa manfaat kecuali untuk lulus tes. Tentu saja saya dapat berpendapat bahwa CarWithFrontSeatsInstalled dan CarWithAllSeatsInstalled adalah tahapan yang secara logis berturut-turut . Risiko metode Bravo nantinya dibutuhkan oleh Alpha kecil, dan bahkan lebih kecil jika saya memainkannya dengan baik. Tetapi tetap saja.
- Saya dapat mengelompokkan operasi yang berbeda, serupa dari jarak jauh, dalam satu operasi.
performAllSeatsInstallation()
. Ini hanya tindakan sementara dan itu meningkatkan kompleksitas operasi tunggal. Jika saya perlu melakukan operasi A dan B dalam urutan yang berbeda, dan saya telah mengemasnya di dalam E = (A + C) dan F (B + D), saya harus melepaskan ikatan E dan F dan mengocok kode sekitar . - Saya dapat menggunakan array fungsi lambda dan menghindari pemeriksaan secara rapi, tapi saya merasa kikuk. Namun ini adalah alternatif terbaik sejauh ini. Itu akan menghilangkan refleksi. Dua masalah yang saya miliki adalah bahwa saya mungkin akan diminta untuk menulis ulang semua objek metode, tidak hanya hipotesis
CarWithEngineInstalled
, dan sementara itu akan menjadi keamanan pekerjaan yang sangat baik, itu benar-benar tidak menarik banyak; dan bahwa pemeriksa cakupan kode memiliki masalah dengan lambdas (yang dapat dipecahkan, tetapi masih ).
Begitu...
- Menurut Anda, mana pilihan terbaik saya?
- Apakah ada cara yang lebih baik yang belum saya pertimbangkan? ( mungkin lebih baik aku berterus terang dan bertanya langsung apa itu? )
- Apakah desain ini cacat tanpa harapan, dan saya lebih baik mengakui kekalahan dan parit - arsitektur ini sama sekali? Tidak baik untuk karir saya, tetapi apakah menulis kode yang dirancang dengan buruk akan menjadi lebih baik dalam jangka panjang?
- Apakah pilihan saya saat ini sebenarnya adalah One True Way, dan saya harus berjuang untuk mendapatkan metrik kualitas yang lebih baik (dan / atau instrumentasi) terinstal? Untuk opsi terakhir ini, saya perlu referensi ... Saya tidak bisa hanya melambaikan tangan ke @PHB sambil bergumam. Ini bukan metrik yang Anda cari . Tidak peduli seberapa besar aku ingin bisa
Jawaban:
Urutan operasi yang panjang sepertinya adalah kelasnya sendiri, yang kemudian membutuhkan objek tambahan untuk melakukan tugasnya. Sepertinya Anda sudah dekat dengan solusi ini. Prosedur panjang ini dapat dipecah menjadi beberapa langkah atau fase. Setiap langkah atau fase dapat berupa kelasnya sendiri yang memperlihatkan metode publik. Anda ingin setiap fase menerapkan antarmuka yang sama. Kemudian jalur perakitan Anda melacak daftar fase.
Untuk membangun contoh perakitan mobil Anda, bayangkan
Car
kelas:Saya akan meninggalkan implementasi beberapa komponen, seperti
IChassis
,Seat
danDashboard
demi singkatnya.Yang
Car
tidak bertanggung jawab untuk membangun itu sendiri. Baris perakitan adalah, jadi mari kita merangkumnya di kelas:Perbedaan penting di sini adalah bahwa CarAssembler memiliki daftar objek fase perakitan yang diimplementasikan
IAssemblyPhase
. Anda sekarang berurusan secara eksplisit dengan metode publik, yang berarti setiap fase dapat diuji dengan sendirinya, dan alat kualitas kode Anda lebih bahagia karena Anda tidak memasukkan begitu banyak ke dalam satu kelas tunggal. Proses perakitan juga mati sederhana. Lewati saja fase-fase dan panggil orang yangAssemble
lewat. Selesai TheIAssemblyPhase
antarmuka juga sederhana mati hanya dengan metode umum tunggal yang mengambil benda Mobil:Sekarang kita perlu kelas konkret kita mengimplementasikan antarmuka ini untuk setiap fase tertentu:
Setiap fase secara individual bisa sangat sederhana. Beberapa hanya satu garis. Beberapa mungkin hanya membutakan menambahkan empat kursi, dan yang lain harus mengkonfigurasi dashboard sebelum menambahkannya ke mobil. Kompleksitas dari proses berlarut-larut ini dibagi menjadi beberapa kelas yang dapat digunakan kembali yang mudah diuji.
Meskipun Anda mengatakan pesanan tidak masalah sekarang, mungkin di masa depan.
Untuk menyelesaikan ini, mari kita lihat proses merakit mobil:
Anda dapat sub-kelas CarAssembler untuk merakit mobil atau truk jenis tertentu. Setiap fase adalah satu unit dalam keseluruhan yang lebih besar, sehingga lebih mudah untuk menggunakan kembali kode.
Jika memutuskan apa yang saya tulis perlu ditulis ulang adalah tanda hitam pada karier saya, maka saya akan bekerja sebagai penggali parit sekarang, dan Anda tidak boleh mengambil nasihat saya. :)
Rekayasa perangkat lunak adalah proses belajar, penerapan, dan kemudian belajar ulang yang abadi. Hanya karena Anda memutuskan untuk menulis ulang kode Anda tidak berarti Anda akan dipecat. Jika itu masalahnya, tidak akan ada insinyur perangkat lunak.
Apa yang telah Anda uraikan bukanlah One True Way, namun ingatlah bahwa metrik kode juga bukan One True Way. Metrik kode harus dilihat sebagai peringatan. Mereka dapat menunjukkan masalah pemeliharaan di masa depan. Mereka adalah pedoman, bukan hukum. Ketika alat metrik kode Anda menunjukkan sesuatu, saya pertama-tama akan menyelidiki kode untuk melihat apakah itu benar menerapkan prinsip-prinsip SOLID . Berkali-kali kode refactoring agar lebih SOLID akan memuaskan alat metrik kode. Terkadang tidak. Anda harus menggunakan metrik ini berdasarkan kasus per kasus.
sumber
Saya tidak tahu apakah ini mungkin bagi Anda, tetapi saya akan berpikir tentang menggunakan pendekatan yang lebih berorientasi data. Idenya adalah bahwa Anda menangkap beberapa (deklaratif) ekspresi aturan & kendala, operasi, dependensi, negara, dll ... Kemudian kelas dan kode Anda lebih umum, berorientasi sekitar mengikuti aturan, menginisialisasi keadaan saat ini, melakukan operasi untuk ubah status, dengan menggunakan tangkapan deklaratif aturan dan ubah status daripada menggunakan kode secara langsung. Seseorang mungkin menggunakan deklarasi data dalam bahasa pemrograman, atau, beberapa alat DSL, atau aturan atau mesin logika.
sumber
Entah bagaimana masalahnya mengingatkan saya pada ketergantungan. Anda ingin mobil dalam konfigurasi tertentu. Seperti Greg Burghardt, saya akan pergi dengan objek / kelas untuk setiap langkah / item / ....
Setiap langkah menyatakan apa yang ditambahkan ke dalam campuran (apa itu
provides
) (bisa beberapa hal), tetapi juga apa yang dibutuhkannya.Kemudian, Anda menentukan konfigurasi akhir yang diperlukan di suatu tempat, dan memiliki algoritma yang melihat apa yang Anda butuhkan, dan memutuskan langkah-langkah mana yang perlu dieksekusi / bagian mana yang perlu ditambahkan / dokumen mana yang perlu dikumpulkan.
Algoritma resolusi ketergantungan tidak terlalu sulit. Kasus paling sederhana adalah di mana setiap langkah menyediakan satu hal, dan tidak ada dua hal yang menyediakan hal yang sama, dan dependensinya sederhana (tidak ada 'atau' dalam dependensi). Untuk kasus yang lebih kompleks: lihat saja alat seperti debian apt atau sesuatu.
Akhirnya, membangun mobil Anda kemudian mengurangi ke langkah apa yang mungkin ada, dan membiarkan CarBuilder mengetahui langkah-langkah yang diperlukan.
Namun satu hal penting adalah Anda harus menemukan cara agar CarBuilder tahu tentang semua langkah. Coba lakukan ini menggunakan file konfigurasi. Menambahkan langkah ekstra hanya akan membutuhkan tambahan pada file konfigurasi. Jika Anda membutuhkan logika, coba tambahkan DSL di file konfigurasi. Jika semuanya gagal, Anda terjebak mendefinisikannya dalam kode.
sumber
Beberapa hal yang Anda sebutkan menonjol sebagai 'buruk' bagi saya
"Logika saya saat ini menggunakan Refleksi untuk kesederhanaan implementasi"
Tidak ada alasan untuk ini. apakah Anda benar-benar menggunakan perbandingan string nama metode untuk menentukan apa yang harus dijalankan ?! Jika Anda mengubah urutan, apakah Anda harus mengubah nama metode ?!
"Operasi dalam satu fase sudah independen"
Jika mereka benar-benar independen satu sama lain, ini menunjukkan bahwa kelas Anda telah mengambil lebih dari satu tanggung jawab. Bagi saya, kedua contoh Anda tampaknya cocok untuk satu jenis tunggal
pendekatan yang akan memungkinkan Anda untuk membagi logika untuk setiap operasi menjadi kelas atau kelas sendiri.
Bahkan saya membayangkan mereka tidak benar-benar independen, saya membayangkan bahwa masing-masing memeriksa keadaan objek dan memodifikasinya, atau setidaknya melakukan semacam validasi sebelum memulai.
Dalam hal ini Anda dapat:
Membuang OOP keluar jendela, dan memiliki layanan yang beroperasi pada data yang terbuka di kelas Anda yaitu.
Service.AddDoorToCar (Mobil Mobil)
Memiliki kelas pembangun yang membangun objek Anda dengan merakit data yang diperlukan terlebih dahulu dan melewati kembali objek yang telah selesai yaitu.
Car = CarBuilder.DenganDoor (). WithDoor (). WithWheels ();
(Logika withX dapat dibagi lagi menjadi subbuilder)
Ambil pendekatan Fungsional dan lulus fungsi / delgates menjadi metode modifikasi
Car.Modify ((c) => c.doors ++);
sumber