Saya baru-baru ini membaca sebuah buku berjudul Pemrograman Fungsional dalam bahasa C # dan saya sadar bahwa sifat pemrograman fungsional yang tidak dapat diubah dan tanpa kewarganegaraan mencapai hasil yang serupa dengan pola injeksi ketergantungan dan mungkin bahkan merupakan pendekatan yang lebih baik, terutama dalam hal pengujian unit.
Saya akan menghargai jika ada orang yang memiliki pengalaman dengan kedua pendekatan dapat berbagi pemikiran dan pengalaman mereka untuk menjawab pertanyaan utama: apakah Pemrograman Fungsional merupakan alternatif untuk pola injeksi ketergantungan?
Jawaban:
Manajemen ketergantungan adalah masalah besar dalam OOP karena dua alasan berikut:
Sebagian besar programmer OO menganggap penggabungan ketat antara data dan kode sepenuhnya menguntungkan, tetapi ada biaya. Mengelola aliran data melalui lapisan adalah bagian yang tidak dapat dihindari dari pemrograman dalam paradigma apa pun. Menggabungkan data dan kode Anda menambah masalah tambahan bahwa jika Anda ingin menggunakan fungsi pada titik tertentu, Anda harus menemukan cara untuk mendapatkan objeknya ke titik itu.
Penggunaan efek samping menciptakan kesulitan yang serupa. Jika Anda menggunakan efek samping untuk beberapa fungsionalitas, tetapi ingin dapat menukar implementasinya, Anda tidak punya pilihan lain selain menyuntikkan ketergantungan itu.
Pertimbangkan sebagai contoh program spammer yang mengikis halaman web untuk alamat email lalu mengirimnya melalui email. Jika Anda memiliki pola pikir DI, saat ini Anda sedang memikirkan layanan yang akan Anda enkapsulasi di belakang antarmuka, dan layanan mana yang akan disuntikkan di mana. Saya akan meninggalkan desain itu sebagai latihan untuk pembaca. Jika Anda memiliki pola pikir FP, saat ini Anda sedang memikirkan input dan output untuk lapisan fungsi terendah, seperti:
Ketika Anda berpikir dalam hal input dan output, tidak ada dependensi fungsi, hanya dependensi data. Itulah yang membuat mereka begitu mudah disatukan. Layer up berikutnya mengatur untuk output dari satu fungsi untuk dimasukkan ke input berikutnya, dan dapat dengan mudah menukar berbagai implementasi sesuai kebutuhan.
Dalam arti yang sangat nyata, pemrograman fungsional secara alami mendorong Anda untuk selalu membalikkan dependensi fungsi Anda, dan karena itu Anda biasanya tidak perlu mengambil tindakan khusus untuk melakukannya setelah fakta. Ketika Anda melakukannya, alat-alat seperti fungsi tingkat tinggi, penutup, dan aplikasi parsial membuatnya lebih mudah dicapai dengan pelat ketel yang lebih sedikit.
Perhatikan bahwa bukan dependensi itu sendiri yang bermasalah. Ketergantungan itu menunjuk ke arah yang salah. Lapisan berikutnya mungkin memiliki fungsi seperti:
Tidak apa-apa untuk lapisan ini untuk memiliki dependensi yang dikodekan secara keras seperti ini, karena tujuan utamanya adalah untuk merekatkan fungsi-fungsi lapisan bawah bersama-sama. Mengganti implementasi sama mudahnya dengan membuat komposisi yang berbeda:
Komposisi ulang yang mudah ini dimungkinkan oleh kurangnya efek samping. Fungsi lapisan bawah sepenuhnya independen satu sama lain. Lapisan berikutnya dapat memilih yang
processText
sebenarnya digunakan berdasarkan beberapa konfigurasi pengguna:Sekali lagi, bukan masalah karena semua dependensi menunjukkan satu arah. Kita tidak perlu membalikkan beberapa dependensi agar semuanya menunjuk dengan cara yang sama, karena fungsi murni sudah memaksa kita untuk melakukannya.
Perhatikan bahwa Anda dapat membuat ini lebih banyak digabungkan dengan melewati
config
lapisan paling bawah daripada memeriksanya di atas. FP tidak mencegah Anda dari melakukan ini, tetapi cenderung membuatnya lebih menyebalkan jika Anda mencoba.sumber
System.String
. Sebuah sistem modul akan membiarkan Anda menggantiSystem.String
dengan variabel sehingga pilihan implementasi string tidak sulit dikodekan, tetapi masih diselesaikan pada waktu kompilasi.Ini mengejutkan saya sebagai pertanyaan aneh. Pendekatan Pemrograman Fungsional sebagian besar bersinggungan dengan injeksi ketergantungan.
Tentu saja, memiliki keadaan tidak berubah dapat mendorong Anda untuk tidak "menipu" dengan memiliki efek samping atau menggunakan status kelas sebagai kontrak implisit antara fungsi. Itu membuat melewati data lebih eksplisit, yang saya kira adalah bentuk paling mendasar dari injeksi ketergantungan. Dan konsep pemrograman fungsional melewati fungsi membuat itu jauh lebih mudah.
Tapi itu tidak menghilangkan ketergantungan. Operasi Anda masih membutuhkan semua data / operasi yang mereka butuhkan ketika keadaan Anda bisa berubah. Dan Anda masih perlu mendapatkan dependensi itu di sana. Jadi saya tidak akan mengatakan bahwa pendekatan pemrograman fungsional menggantikan DI sama sekali, jadi tidak ada alternatif.
Jika ada, mereka baru saja menunjukkan kepada Anda betapa buruknya kode OO dapat membuat dependensi implisit daripada yang jarang dipikirkan oleh programmer.
sumber
Jawaban cepat untuk pertanyaan Anda adalah: Tidak .
Tetapi seperti yang telah dinyatakan oleh orang lain, pertanyaan itu menikahi dua, konsep yang agak tidak terkait.
Mari kita lakukan langkah demi langkah.
DI menghasilkan gaya non-fungsional
Pada intinya pemrograman fungsi adalah fungsi murni - fungsi yang memetakan input ke output, sehingga Anda selalu mendapatkan output yang sama untuk input yang diberikan.
DI biasanya berarti unit Anda tidak lagi murni karena output dapat bervariasi tergantung pada injeksi. Misalnya, dalam fungsi berikut:
getBookedSeatCount
(fungsi) dapat bervariasi menghasilkan hasil yang berbeda untuk input yang diberikan sama. Ini membuatbookSeats
najis juga.Ada pengecualian untuk ini - Anda dapat menyuntikkan salah satu dari dua algoritma pengurutan yang menerapkan pemetaan input-output yang sama, meskipun menggunakan algoritma yang berbeda. Tapi ini pengecualian.
Suatu sistem tidak dapat murni
Fakta bahwa suatu sistem tidak dapat murni sama-sama diabaikan seperti yang dinyatakan dalam sumber pemrograman fungsional.
Suatu sistem harus memiliki efek samping dengan contoh-contoh nyata adalah:
Jadi bagian dari sistem Anda harus melibatkan efek samping dan bagian itu mungkin juga melibatkan gaya imperatif, atau gaya OO.
Paradigma shell-core
Meminjam istilah-istilah dari perbincangan hebat Gary Bernhardt tentang batasan , arsitektur sistem (atau modul) yang baik akan mencakup dua lapisan ini:
Kuncinya adalah untuk 'membagi' sistem menjadi bagian murni (inti) dan bagian tidak murni (shell).
Meskipun menawarkan solusi yang sedikit cacat (dan kesimpulan), artikel Mark Seemann ini mengusulkan konsep yang sama. Implementasi Haskell sangat berwawasan karena ini menunjukkan bahwa semuanya dapat dilakukan dengan menggunakan FP.
DI dan FP
Mempekerjakan DI sangat masuk akal bahkan jika sebagian besar aplikasi Anda murni. Kuncinya adalah membatasi DI di dalam shell yang tidak murni.
Contohnya adalah bertopik API - Anda ingin API yang sebenarnya dalam produksi, tetapi gunakan bertopik dalam pengujian. Mengikuti model shell-core akan sangat membantu di sini.
Kesimpulan
Jadi FP dan DI bukan alternatif. Anda mungkin memiliki keduanya di sistem Anda, dan sarannya adalah untuk memastikan pemisahan antara bagian yang murni dan tidak murni dari sistem, di mana FP dan DI berada masing-masing.
sumber
Dari sudut pandang OOP, fungsi dapat dianggap sebagai antarmuka metode tunggal.
Antarmuka adalah kontrak yang lebih kuat daripada fungsi.
Jika Anda menggunakan pendekatan fungsional dan melakukan banyak DI maka dibandingkan dengan menggunakan pendekatan OOP Anda akan mendapatkan lebih banyak kandidat untuk setiap ketergantungan.
vs.
sumber