Saya menemukan kutipan ini di " The Joy of Clojure " di hlm. 32, tetapi seseorang mengatakan hal yang sama kepada saya saat makan malam minggu lalu dan saya sudah mendengarnya di tempat lain juga:
[A] Kelemahan dari pemrograman berorientasi objek adalah hubungan erat antara fungsi dan data.
Saya mengerti mengapa kopling yang tidak perlu buruk dalam aplikasi. Juga saya merasa nyaman mengatakan bahwa keadaan yang dapat berubah dan warisan harus dihindari, bahkan dalam Pemrograman Berorientasi Objek. Tapi saya gagal melihat mengapa menempel fungsi di kelas pada dasarnya buruk.
Maksud saya, menambahkan fungsi ke kelas sepertinya menandai email di Gmail, atau menempelkan file di folder. Ini adalah teknik organisasi yang membantu Anda menemukannya lagi. Anda memilih beberapa kriteria, lalu menyatukan semuanya. Sebelum OOP, program kami adalah sekumpulan besar metode dalam file. Maksud saya, Anda harus meletakkan fungsi di suatu tempat. Mengapa tidak mengaturnya?
Jika ini adalah serangan terselubung pada tipe, mengapa mereka tidak mengatakan bahwa membatasi tipe input dan output ke suatu fungsi adalah salah? Saya tidak yakin apakah saya bisa setuju dengan itu, tapi setidaknya saya akrab dengan argumen pro dan con type safety. Bagi saya ini kedengarannya seperti masalah yang terpisah.
Tentu, kadang-kadang orang salah dan menempatkan fungsionalitas di kelas yang salah. Tetapi dibandingkan dengan kesalahan lain, ini tampak seperti ketidaknyamanan yang sangat kecil.
Jadi, Clojure memiliki ruang nama. Bagaimana menempelkan fungsi pada kelas di OOP berbeda dari menempelkan fungsi di ruang nama di Clojure dan mengapa begitu buruk? Ingat, fungsi dalam kelas tidak harus beroperasi hanya pada anggota kelas itu. Lihatlah java.lang.StringBuilder - ini beroperasi pada semua jenis referensi, atau melalui tinju otomatis, pada jenis apa pun.
PS Kutipan ini merujuk pada sebuah buku yang belum saya baca: Pemrograman Multiparadigma di Leda: Timothy Budd, 1995 .
Jawaban:
Secara teori, kopling fungsi-data longgar membuatnya lebih mudah untuk menambahkan lebih banyak fungsi untuk bekerja pada data yang sama. Sisi buruknya adalah membuatnya lebih sulit untuk mengubah struktur data itu sendiri, yang mengapa dalam prakteknya, kode fungsional yang dirancang dengan baik dan kode OOP yang dirancang dengan baik memiliki tingkat penggandaan yang sangat mirip.
Ambil diarahkan grafik asiklik (DAG) sebagai contoh struktur data. Dalam pemrograman fungsional, Anda masih memerlukan beberapa abstraksi untuk menghindari pengulangan, jadi Anda akan membuat modul dengan fungsi untuk menambah dan menghapus node dan edge, menemukan node dapat dijangkau dari node yang diberikan, membuat penyortiran topologi, dll. Fungsi-fungsi tersebut secara efektif erat digabungkan ke data, meskipun kompilator tidak menegakkannya. Anda dapat menambahkan simpul dengan cara yang sulit, tetapi mengapa Anda menginginkannya? Kekompakan dalam satu modul mencegah kopling ketat di seluruh sistem.
Sebaliknya di sisi OOP, fungsi apa pun selain operasi DAG dasar akan dilakukan dalam kelas "tampilan" yang terpisah, dengan objek DAG diteruskan sebagai parameter. Sangat mudah untuk menambahkan sebanyak mungkin tampilan yang Anda inginkan yang beroperasi pada data DAG, menciptakan tingkat decoupling fungsi-data yang sama seperti yang Anda temukan dalam program fungsional. Kompiler tidak akan mencegah Anda menjejalkan semuanya ke dalam satu kelas, tetapi kolega Anda akan melakukannya.
Mengubah paradigma pemrograman tidak mengubah praktik abstraksi, kohesi, dan penggabungan terbaik, melainkan hanya mengubah praktik mana yang dikompilasi oleh kompiler untuk membantu Anda menegakkan. Dalam pemrograman fungsional, ketika Anda menginginkan penggabungan fungsi-data, ini diberlakukan oleh persetujuan tuan-tuan daripada kompiler. Dalam OOP, pemisahan model-view dipaksakan oleh persetujuan tuan-tuan daripada kompiler.
sumber
Jika Anda tidak tahu, ini sudah mengambil wawasan ini: Konsep berorientasi objek dan penutupan adalah dua sisi dari koin yang sama. Yang mengatakan, apa itu penutupan? Dibutuhkan variabel (s) atau data dari lingkup sekitarnya dan mengikatnya di dalam fungsi, atau dari perspektif OO Anda secara efektif melakukan hal yang sama ketika Anda, misalnya, mengirimkan sesuatu ke konstruktor sehingga nantinya Anda dapat menggunakannya sepotong data dalam fungsi anggota instance itu. Tetapi mengambil hal-hal dari lingkup sekitar bukanlah hal yang baik untuk dilakukan - semakin besar lingkup sekitarnya, semakin jahat untuk melakukan ini (meskipun secara pragmatis, beberapa kejahatan sering diperlukan untuk menyelesaikan pekerjaan). Penggunaan variabel global membawa ini ke ekstrem, di mana fungsi dalam suatu program menggunakan variabel pada lingkup program - benar-benar jahat. Adadeskripsi yang baik di tempat lain tentang mengapa variabel global itu jahat.
Jika Anda mengikuti teknik OO Anda pada dasarnya sudah menerima bahwa setiap modul dalam program Anda akan memiliki tingkat kejahatan minimum tertentu. Jika Anda mengambil pendekatan fungsional untuk pemrograman, Anda bertujuan untuk ideal di mana tidak ada modul dalam program Anda yang mengandung kejahatan penutupan, meskipun Anda mungkin masih memiliki beberapa, tetapi akan jauh lebih sedikit daripada OO.
Itulah kelemahan dari OO - ini mendorong kejahatan semacam ini, menggabungkan data berfungsi dengan membuat penutupan standar (semacam teori pemrograman jendela yang rusak ).
Satu-satunya sisi positifnya adalah, jika Anda tahu Anda akan menggunakan banyak penutupan untuk memulai, OO setidaknya memberi Anda kerangka kerja yang ideal untuk membantu mengatur pendekatan itu sehingga rata-rata programmer dapat memahaminya. Secara khusus variabel yang ditutup lebih eksplisit dalam konstruktor daripada hanya diambil secara implisit dalam penutupan fungsi. Program fungsional yang menggunakan banyak penutupan seringkali lebih samar daripada program OO yang setara, meskipun tidak selalu kurang elegan :)
sumber
Ini tentang jenis kopling:
Fungsi yang dibangun menjadi objek untuk bekerja pada objek itu tidak dapat digunakan pada jenis objek lainnya.
Di Haskell Anda menulis fungsi untuk bekerja melawan kelas tipe - jadi ada banyak jenis objek yang dapat digunakan fungsi apa pun, asalkan itu adalah jenis kelas yang diberikan yang berfungsi.
Fungsi bebas memungkinkan decoupling seperti itu yang tidak Anda dapatkan ketika Anda fokus pada penulisan fungsi Anda untuk bekerja di dalam tipe A karena Anda tidak dapat menggunakannya jika Anda tidak memiliki instance tipe A, meskipun fungsi tersebut mungkin jika tidak cukup umum untuk digunakan pada instance B tipe atau instance C.
sumber
Di Java dan inkarnasi serupa OOP, metode instance (tidak seperti fungsi gratis atau metode ekstensi) tidak dapat ditambahkan dari modul lain.
Ini menjadi lebih dari batasan ketika Anda mempertimbangkan antarmuka yang hanya dapat diimplementasikan dengan metode instance. Anda tidak dapat mendefinisikan antarmuka dan kelas dalam modul yang berbeda dan kemudian menggunakan kode dari modul ketiga untuk mengikat mereka bersama. Pendekatan yang lebih fleksibel, seperti kelas tipe Haskell harus bisa melakukan itu.
sumber
Orientasi Objek pada dasarnya tentang Abstraksi Data Prosedural (atau Abstraksi Data Fungsional jika Anda menghilangkan efek samping yang merupakan masalah ortogonal). Dalam arti tertentu, Kalkulus Lambda adalah bahasa Berorientasi Objek tertua dan paling murni, karena hanya menyediakan Abstraksi Data Fungsional (karena tidak memiliki konstruksi selain fungsi).
Hanya operasi dari a satu objek yang dapat memeriksa representasi data objek itu. Bahkan objek lain dengan tipe yang sama tidak dapat melakukannya. (Ini adalah perbedaan utama antara Abstraksi Data Berorientasi Objek dan Tipe Data Abstrak: dengan ADT, objek dengan tipe yang sama dapat memeriksa representasi data satu sama lain, hanya representasi objek dari tipe lain yang disembunyikan.)
Ini artinya beberapa objek dengan tipe yang sama mungkin memiliki representasi data yang berbeda. Bahkan objek yang sama mungkin memiliki representasi data yang berbeda pada waktu yang berbeda. (Misalnya, dalam Scala,
Map
s danSet
s beralih antara array dan hash trie tergantung pada jumlah elemen karena untuk jumlah yang sangat kecil pencarian linear dalam array lebih cepat daripada pencarian logaritma di pohon pencarian karena faktor konstan yang sangat kecil .)Dari luar objek, Anda seharusnya tidak, Anda tidak bisa tahu representasi datanya. Itu kebalikan dari kopling ketat.
sumber
Kopling ketat antara data dan fungsi buruk karena Anda ingin dapat mengubah satu sama lain secara independen dan kopling ketat membuat ini sulit karena Anda tidak dapat mengubah satu tanpa sepengetahuan dan mungkin mengubah, yang lain.
Anda ingin data yang berbeda disajikan ke fungsi untuk tidak memerlukan perubahan dalam fungsi dan Anda ingin dapat membuat perubahan pada fungsi tanpa memerlukan perubahan apa pun pada data yang dioperasikan untuk mendukung perubahan fungsi tersebut.
sumber