Mengapa model domain anemik dianggap buruk di C # / OOP, tetapi sangat penting dalam F # / FP?

46

Dalam posting blog di F # untuk kesenangan dan keuntungan, dikatakan:

Dalam desain fungsional, sangat penting untuk memisahkan perilaku dari data. Tipe data sederhana dan "bodoh". Dan kemudian secara terpisah, Anda memiliki sejumlah fungsi yang bekerja pada tipe data tersebut.

Ini adalah kebalikan dari desain berorientasi objek, di mana perilaku dan data dimaksudkan untuk digabungkan. Lagipula, itulah kelas sebenarnya. Dalam desain yang benar-benar berorientasi objek, Anda seharusnya hanya memiliki perilaku - data bersifat pribadi dan hanya dapat diakses melalui metode.

Bahkan, dalam OOD, tidak memiliki perilaku yang cukup di sekitar tipe data dianggap sebagai Hal yang Buruk, dan bahkan memiliki nama: " model domain anemik ".

Mengingat bahwa dalam C # kita tampaknya terus meminjam dari F #, dan mencoba untuk menulis lebih banyak kode gaya fungsional; kenapa kita tidak meminjam ide untuk memisahkan data / perilaku, dan bahkan menganggapnya buruk? Apakah hanya karena definisi tidak dengan OOP, atau ada alasan konkret bahwa itu buruk di C # yang karena beberapa alasan tidak berlaku di F # (dan pada kenyataannya, dibalik)?

(Catatan: Saya secara khusus tertarik pada perbedaan dalam C # / F # yang dapat mengubah pendapat tentang apa yang baik / buruk, daripada individu yang mungkin tidak setuju dengan pendapat dalam posting blog).

Danny Tuppeny
sumber
1
Hai Dan! Aditude Anda sangat menginspirasi. Selain platform .NET (dan haskell) saya mendorong Anda untuk melihat scala. Debashish ghosh telah menulis beberapa blog tentang pemodelan domain dengan alat fungsional, itu wawasan bagi saya, semoga bagi Anda juga, ini dia: debasishg.blogspot.com/2012/01/…
AndreasScheinert
2
Saya dikirimi posting blog yang menarik oleh seorang rekan hari ini: blog.inf.ed.ac.uk/sapm/2014/02/04/... Tampaknya orang-orang mulai menentang gagasan bahwa model domain anemik benar-benar buruk; yang saya pikir mungkin hal yang baik!
Danny Tuppeny
1
Posting blog yang Anda referensikan didasarkan pada ide yang keliru: "Biasanya baik-baik saja untuk data diekspos tanpa dienkapsulasi. Data tidak berubah, sehingga tidak bisa" rusak "oleh fungsi yang tidak sesuai." Bahkan tipe yang tidak berubah memiliki invarian yang perlu dilestarikan, dan itu membutuhkan data yang disembunyikan dan mengendalikan bagaimana hal itu dapat dibuat. Misalnya, Anda tidak dapat mengekspos penerapan pohon merah-hitam abadi, karena seseorang dapat membuat pohon yang hanya terdiri dari simpul merah.
Doval
4
@Doval bersikap adil itu seperti mengatakan Anda tidak dapat mengekspos penulis sistem file karena seseorang mungkin mengisi disk Anda. Seseorang yang membuat pohon hanya dengan simpul merah sama sekali tidak merusak pohon merah-hitam tempat mereka dikloning, atau kode apa pun di seluruh sistem yang kebetulan menggunakan instance yang terbentuk dengan baik itu. Jika Anda menulis kode yang secara aktif membuat contoh sampah baru atau melakukan hal-hal berbahaya, ketidakmampuan tidak akan menyelamatkan Anda, tetapi itu akan menyelamatkan orang lain dari Anda . Menyembunyikan implementasi tidak akan menghentikan orang dari menulis kode omong kosong yang akhirnya membaginya dengan nol.
Jimmy Hoffa
2
@ JimmyHoffa saya setuju, tapi itu tidak ada hubungannya dengan apa yang saya kritik. Penulis mengklaim bahwa biasanya baik-baik saja bagi data untuk diekspos karena tidak dapat diubah, dan saya mengatakan bahwa ketidakmampuan tidak secara ajaib menghilangkan kebutuhan untuk menyembunyikan detail implementasi.
Doval

Jawaban:

37

Alasan utama FP bertujuan untuk ini dan C # OOP tidak adalah bahwa dalam FP fokusnya adalah pada transparansi referensial; yaitu, data masuk ke fungsi dan data keluar, tetapi data asli tidak berubah.

Dalam C # OOP ada konsep pendelegasian tanggung jawab di mana Anda mendelegasikan manajemen objek untuk itu, dan karena itu Anda ingin itu mengubah internalnya sendiri.

Dalam FP Anda tidak pernah ingin mengubah nilai-nilai dalam suatu objek, oleh karena itu memiliki fungsi Anda tertanam di objek Anda tidak masuk akal.

Lebih jauh dalam FP Anda memiliki polimorfisme jenis yang lebih tinggi yang memungkinkan fungsi Anda menjadi lebih umum daripada C # OOP. Dengan cara ini Anda dapat menulis fungsi yang berfungsi untuk apa pun a, dan karenanya memasukkannya ke dalam blok data tidak masuk akal; yang akan erat beberapa metode sehingga hanya bekerja dengan yang khusus jenis dari a. Perilaku seperti itu baik-baik saja dan umum di C # OOP karena Anda tidak memiliki kemampuan untuk melakukan fungsi abstrak secara umum, tetapi dalam FP itu merupakan tradeoff.

Masalah terbesar yang pernah saya lihat dalam model domain anemia di C # OOP adalah Anda berakhir dengan kode duplikat karena Anda memiliki DTO x, dan 4 fungsi berbeda yang melakukan aktivitas f ke DTO x karena 4 orang berbeda tidak melihat implementasi lainnya . Ketika Anda meletakkan metode ini langsung pada DTO x, maka ke-4 orang itu semua melihat implementasi f dan menggunakannya kembali.

Model data anemia dalam C # OOP menggunakan kembali kode penghambat, tetapi ini bukan kasus di FP karena satu fungsi digeneralisasi di berbagai jenis sehingga Anda mendapatkan penggunaan kembali kode yang lebih besar karena fungsi tersebut dapat digunakan dalam banyak skenario daripada fungsi yang Anda gunakan akan menulis untuk DTO tunggal dalam C #.


Seperti yang ditunjukkan dalam komentar , inferensi tipe adalah salah satu manfaat yang diandalkan FP untuk memungkinkan polimorfisme yang signifikan, dan secara khusus Anda dapat melacak ini kembali ke sistem tipe Hindley Milner dengan inferensi tipe Algoritma W; inferensi tipe seperti itu dalam sistem tipe C # OOP dihindari karena waktu kompilasi ketika inferensi berbasis kendala ditambahkan menjadi sangat lama karena diperlukan pencarian yang lengkap, detail di sini: https://stackoverflow.com/questions/3968834/generics-why -cant-the-compiler-infer-the-type-argumen-dalam-hal ini

Jimmy Hoffa
sumber
Fitur apa di F # yang membuatnya lebih mudah untuk menulis kode yang dapat digunakan kembali semacam ini? Mengapa kode dalam C # tidak dapat digunakan kembali? (Saya pikir saya melihat kemampuan untuk memiliki metode mengambil argumen dengan properti tertentu, tanpa memerlukan antarmuka; yang saya kira akan menjadi kunci?)
Danny Tuppeny
7
@DannyTuppeny jujur ​​F # adalah contoh buruk untuk perbandingan, itu hanya sedikit berpakaian C #; itu bahasa imperatif yang bisa berubah seperti C #, ia memiliki beberapa fasilitas FP yang tidak dimiliki C # tetapi tidak banyak. Lihatlah haskell untuk melihat di mana FP benar-benar menonjol dan hal-hal seperti ini menjadi jauh lebih mungkin karena kelas jenis serta ADT generik
Jimmy Hoffa
@MattFenwick Saya merujuk secara eksplisit ke C # dalam hal itu karena itulah yang ditanyakan oleh poster itu. Di mana saya merujuk ke OOP di seluruh jawaban saya di sini yang saya maksud C # OOP, saya akan mengedit untuk mengklarifikasi.
Jimmy Hoffa
2
Fitur umum di antara bahasa fungsional yang memungkinkan jenis penggunaan ulang ini adalah pengetikan dinamis atau disimpulkan. Meskipun bahasa itu sendiri dapat menggunakan tipe data yang terdefinisi dengan baik, fungsi tipikal tidak peduli apa data itu, selama operasi (fungsi lain atau aritmatika) yang dilakukan pada mereka valid. Ini juga tersedia dalam paradigma OO (Go, misalnya, memiliki implementasi antarmuka implisit yang memungkinkan suatu objek menjadi Bebek, karena ia dapat Terbang, Berenang dan Dukun, tanpa objek yang secara eksplisit dinyatakan sebagai Bebek) tetapi cukup banyak persyaratan pemrograman fungsional.
KeithS
4
@KeithS Menurut kelebihan nilai berbasis di Haskell Saya pikir maksud Anda pencocokan pola. Kemampuan Haskell untuk memiliki beberapa fungsi level atas dengan nama yang sama dengan desugars pola yang berbeda segera ke 1 fungsi level atas + kecocokan pola.
jozefg
6

Mengapa model domain anemik dianggap buruk di C # / OOP, tetapi sangat penting dalam F # / FP?

Pertanyaan Anda memiliki masalah besar yang akan membatasi kegunaan jawaban yang Anda dapatkan: Anda menyiratkan / menganggap bahwa F # dan FP serupa. FP adalah keluarga besar bahasa termasuk penulisan ulang simbolis, dinamis dan statis. Bahkan di antara bahasa FP yang diketik secara statis ada banyak teknologi berbeda untuk mengekspresikan model domain seperti modul tingkat tinggi di OCaml dan SML (yang tidak ada di F #). F # adalah salah satu dari bahasa fungsional ini tetapi sangat terkenal karena lean dan, khususnya, tidak menyediakan modul tingkat tinggi atau jenis yang lebih tinggi.

Bahkan, saya tidak bisa mulai memberi tahu Anda bagaimana model domain diungkapkan dalam FP. Jawaban lain di sini berbicara sangat spesifik tentang bagaimana hal itu dilakukan di Haskell dan sama sekali tidak berlaku untuk Lisp (ibu dari semua bahasa FP), keluarga bahasa ML atau bahasa fungsional lainnya.

kenapa kita tidak meminjam ide untuk memisahkan data / perilaku, dan bahkan menganggapnya buruk?

Obat generik dapat dianggap sebagai cara memisahkan data dan perilaku. Generik berasal dari keluarga ML bahasa pemrograman fungsional yang bukan bagian dari OOP. C # memiliki obat generik, tentu saja. Jadi orang dapat berargumentasi bahwa C # perlahan meminjam ide untuk memisahkan data dan perilaku.

Apakah hanya karena definisi itu tidak sesuai dengan OOP,

Saya percaya OOP didasarkan pada premis yang berbeda secara mendasar dan, akibatnya, tidak memberi Anda alat yang Anda butuhkan untuk memisahkan data dan perilaku. Untuk semua tujuan praktis, Anda memerlukan produk dan jumlah tipe data dan mengirimkannya. Dalam ML ini berarti penyatuan dan pencatatan jenis dan pencocokan pola.

Lihatlah contoh yang saya berikan di sini .

atau apakah ada alasan konkret bahwa itu buruk di C # yang karena alasan tertentu tidak berlaku di F # (dan pada kenyataannya, dibalik)?

Hati-hati melompat dari OOP ke C #. C # sama sekali tidak puritan tentang OOP seperti bahasa lainnya. NET Framework. Sekarang penuh dengan generik, metode statis dan bahkan lambda.

(Catatan: Saya secara khusus tertarik pada perbedaan dalam C # / F # yang dapat mengubah pendapat tentang apa yang baik / buruk, daripada individu yang mungkin tidak setuju dengan pendapat dalam posting blog).

Kurangnya jenis penyatuan dan pencocokan pola dalam C # membuatnya hampir mustahil untuk dilakukan. Ketika semua yang Anda miliki adalah palu, semuanya tampak seperti paku ...

Jon Harrop
sumber
2
... ketika semua yang Anda miliki adalah palu, rakyat OOP akan membuat palu pabrik; P +1 untuk benar-benar menunjukkan inti dari apa yang tidak ada pada C # untuk memungkinkan data dan perilaku sepenuhnya diabstraksi dari satu sama lain: Jenis dan pencocokan pola penyatuan.
Jimmy Hoffa
-4

Saya pikir dalam aplikasi bisnis Anda sering tidak ingin menyembunyikan data karena pencocokan pola pada nilai yang tidak berubah bagus untuk memastikan bahwa Anda menutupi semua kasus yang mungkin. Tetapi jika Anda menerapkan algoritma atau struktur data yang kompleks, Anda lebih baik menyembunyikan detail implementasi yang mengubah ADT (tipe data aljabar) menjadi ADT (tipe data abstrak).

Giacomo Citi
sumber
4
Bisakah Anda menjelaskan lebih banyak tentang bagaimana ini berlaku untuk pemrograman berorientasi objek vs pemrograman fungsional?