“Gunakan peta sebagai ganti kelas untuk merepresentasikan data” -Rich Hickey

19

Dalam video ini oleh Rich Hickey , pencipta Clojure, ia menyarankan untuk menggunakan peta untuk mewakili data alih-alih menggunakan kelas untuk mewakilinya, seperti yang dilakukan di Jawa. Saya tidak mengerti bagaimana ini bisa lebih baik, karena bagaimana pengguna API dapat mengetahui apa kunci input jika mereka hanya direpresentasikan sebagai peta.

Contoh :

PersonAPI {
    Person addPerson(Person obj);
    Map<String, Object> addPerson(Map<String, Object> personMap);
}

Dalam fungsi kedua bagaimana pengguna API dapat mengetahui apa saja input untuk membuat seseorang?

Emil
sumber
Saya juga ingin tahu ini dan saya merasa bahwa contoh pertanyaan tidak cukup menjawabnya.
sydan
Saya tahu saya pernah melihat diskusi ini sebelumnya di suatu tempat di SE. Saya percaya itu dalam konteks JavaScript, tetapi argumennya sama. Tidak dapat menemukannya.
Sebastian Redl
2
Nah karena Clojure adalah seorang Lisp, Anda harus melakukan hal-hal yang sesuai dengan Lisp. ketika Anda menggunakan Java, kode dalam ... well Java.
AK_

Jawaban:

12

Ringkasan Eksaggitif (TM)

Anda mendapatkan beberapa hal.

  • Warisan dan kloning prototipe
  • Penambahan dinamis properti baru
  • Keberadaan bersama dari berbagai versi (tingkat spesifikasi) yang berbeda dari kelas yang sama.
    • Objek milik versi yang lebih baru (tingkat spesifikasi) akan memiliki sifat "opsional" tambahan.
  • Introspeksi properti, lama dan baru
  • Introspeksi aturan validasi (dibahas di bawah)

Ada satu kelemahan fatal.

  • Compiler tidak memeriksa string yang salah eja untuk Anda.
  • Alat refactoring otomatis tidak akan mengganti nama nama kunci properti untuk Anda - kecuali jika Anda membayar yang mewah.

Masalahnya, Anda bisa mendapatkan introspeksi dengan menggunakan, um, introspeksi. Inilah yang biasanya terjadi:

  • Aktifkan refleksi.
  • Tambahkan perpustakaan introspeksi besar ke dalam proyek Anda.
  • Tandai berbagai metode dan properti objek dengan atribut atau anotasi.
  • Biarkan perpustakaan introspeksi melakukan keajaiban.

Dengan kata lain, jika Anda tidak perlu berinteraksi dengan FP, Anda tidak harus mengikuti saran Rich Hickey.

Terakhir, tetapi tidak sedikit (atau paling cantik), meskipun menggunakan Stringsebagai kunci properti masuk akal, Anda tidak harus menggunakan Strings. Banyak sistem lama, termasuk Android ™, menggunakan ID integer secara ekstensif melalui seluruh kerangka kerja untuk merujuk ke kelas, properti, sumber daya, dll.

Android adalah merek dagang dari Google Inc.


Anda juga bisa membuat kedua dunia bahagia.

Untuk dunia Jawa, terapkan getter dan setter seperti biasa.

Untuk dunia FP, terapkan

  • Object getPropertyByName(String name)
  • void setPropertyByName(String name, Object value) throws IllegalPropertyChangeException
  • List<String> getPropertyNames()
  • Class<?> getPropertyValueClass(String name)

Di dalam fungsi ini, ya, kode jelek, tetapi ada plugin IDE yang akan mengisinya untuk Anda, menggunakan ... eh, plugin pintar yang membaca kode Anda.

Sisi Jawa hal-hal akan sama seperti pemain seperti biasa. Mereka tidak akan pernah menggunakan bagian kode yang jelek itu . Anda bahkan mungkin ingin menyembunyikannya dari Javadoc.

Sisi FP dunia dapat menulis kode "leet" apa pun yang mereka inginkan, dan mereka biasanya tidak meneriaki Anda tentang kode yang lambat.


Secara umum, menggunakan peta (tas properti) di tempat objek adalah hal yang lumrah dalam pengembangan perangkat lunak. Ini tidak unik untuk pemrograman fungsional atau jenis bahasa tertentu. Ini mungkin bukan pendekatan idiomatis untuk bahasa tertentu, tetapi ada situasi yang mengharuskannya.

Secara khusus, serialisasi / deserialisasi sering membutuhkan teknik yang serupa.

Hanya beberapa pemikiran umum tentang "peta sebagai objek".

  1. Anda masih harus menyediakan fungsi untuk memvalidasi "peta sebagai objek". Perbedaannya adalah bahwa "peta sebagai objek" memungkinkan untuk kriteria validasi yang lebih fleksibel (kurang ketat).
  2. Anda dapat dengan mudah menambahkan bidang tambahan ke "peta sebagai objek".
  3. Untuk memberikan spesifikasi persyaratan minimum objek yang valid, Anda harus:
    • Tuliskan set kunci "yang diperlukan minimal" yang diharapkan di peta
    • Untuk setiap kunci yang nilainya perlu divalidasi, berikan fungsi validasi nilai
    • Jika ada aturan validasi yang perlu memeriksa beberapa nilai kunci, berikan juga.
    • Apa manfaatnya? Memberikan spesifikasi dengan cara ini introspektif: Anda dapat menulis sebuah program untuk menanyakan set kunci yang diperlukan minimal, dan untuk mendapatkan fungsi validasi untuk setiap kunci.
    • Dalam OOP, semua ini digulung menjadi kotak hitam, atas nama "enkapsulasi". Di tempat logika validasi yang dapat dibaca mesin, pemanggil hanya dapat membaca "dokumentasi API" yang dapat dibaca manusia (jika untungnya ada).
rwong
sumber
commonplacetampaknya sedikit kuat bagiku. Maksud saya ini digunakan seperti yang Anda gambarkan, tetapi juga salah satu dari hal-hal yang terkenal tidak rapuh / rapuh (seperti byte array atau bare pointer) yang dicoba oleh perpustakaan untuk disembunyikan.
Telastyn
@ Telastyn "Kepala jelek seribu ular" ini biasanya terjadi pada batas komunikasi antara dua sistem, di mana untuk beberapa alasan saluran komunikasi atau antar-proses tidak memungkinkan objek yang akan diteleportasi utuh. Saya kira teknik baru seperti Protokol Buffer hampir menghilangkan kasus penggunaan peta ini sebagai objek. Mungkin masih ada kasus penggunaan lain yang valid, tetapi saya memiliki sedikit pengetahuan tentang itu.
rwong
2
Adapun kelemahan fatal, setuju. Tetapi, jika nama kunci properti "mudah salah eja" dan "sulit diperbaiki" disimpan, sebanyak mungkin, dalam konstanta atau enum , masalah itu hilang. Tentu saja, itu membatasi ekstensibilitas beberapa :-(.
user949300
Jika "satu kelemahan fatal" benar-benar fatal, mengapa beberapa orang dapat menggunakannya secara efektif. Juga, kelas dan pengetikan statis bersifat ortogonal - Anda dapat mendefinisikan kelas di Clojure, meskipun diketik secara dinamis.
Nathan Davis
@NathanDavis (1) Saya akui jawaban saya ditulis dari perspektif pengetikan statis (C #) dan saya menulis jawaban ini karena saya memiliki sudut pandang yang sama dengan penanya. Saya akui saya tidak memiliki sudut pandang FP-sentris. (2) Selamat datang di SE.SE, dan karena Anda adalah sosok yang disegani di Clojure, silakan luangkan waktu untuk menuliskan jawaban Anda sendiri jika jawaban yang ada tidak memuaskan. Downvotes mengurangi reputasi dan jawaban baru menarik upvotes yang menambah reputasi dengan cepat. (3) Saya dapat melihat bagaimana "objek tidak lengkap" dapat berguna - Anda dapat meminta 2 properti untuk objek tertentu (nama, avatar) dan mengabaikan sisanya.
rwong
9

Itu pembicaraan yang sangat baik oleh seseorang yang benar-benar tahu apa yang dia bicarakan. Saya sarankan pembaca memperhatikan semuanya. Hanya 36 menit lamanya.

Salah satu poin utamanya adalah kesederhanaan membuka peluang untuk perubahan di kemudian hari. Memilih kelas untuk mewakili a Personmemberikan manfaat langsung dengan membuat API yang dapat diverifikasi secara statis, seperti yang Anda tunjukkan, tetapi itu datang dengan biaya membatasi peluang atau meningkatkan biaya untuk perubahan dan digunakan kembali nanti.

Maksudnya adalah bahwa menggunakan kelas mungkin merupakan pilihan yang masuk akal, tetapi harus menjadi pilihan sadar yang datang dengan kesadaran penuh terhadap biayanya, dan programmer secara tradisional melakukan pekerjaan yang sangat buruk memperhatikan biaya-biaya itu, apalagi mempertimbangkannya. Pilihan itu harus dievaluasi kembali ketika kebutuhan Anda tumbuh.

Berikut ini adalah beberapa perubahan kode (satu atau dua di antaranya disebutkan dalam pembicaraan) yang berpotensi lebih mudah menggunakan daftar peta dibandingkan dengan menggunakan daftar Personobjek:

  • Mengirim seseorang ke server REST. (Fungsi yang dibuat untuk menempatkan Mapprimitif ke dalam format yang dapat ditransmisikan sangat dapat digunakan kembali dan bahkan mungkin disediakan di perpustakaan. PersonObjek mungkin membutuhkan kode khusus untuk menyelesaikan pekerjaan yang sama).
  • Secara otomatis membangun daftar orang dari permintaan basis data relasional. (Sekali lagi, satu fungsi generik dan sangat dapat digunakan kembali).
  • Secara otomatis menghasilkan formulir untuk menampilkan dan mengedit seseorang.
  • Gunakan fungsi umum untuk bekerja dengan data orang yang sangat tidak homogen, seperti siswa versus karyawan.
  • Dapatkan daftar semua orang yang tinggal di kode pos tertentu.
  • Gunakan kembali kode itu untuk mendapatkan daftar semua bisnis dalam kode pos tertentu.
  • Tambahkan bidang khusus pelanggan ke seseorang tanpa memengaruhi klien lain.

Kami memecahkan masalah semacam ini sepanjang waktu, dan memiliki pola dan alat untuk itu, tetapi jarang berhenti untuk memikirkan jika memilih representasi data yang lebih sederhana dan lebih fleksibel pada awalnya akan membuat pekerjaan kami lebih mudah.

Karl Bielefeldt
sumber
Apakah ada nama untuk ini? Katakanlah, Pemetaan Objek-Properti atau Pemetaan Objek-Atribut (sepanjang garis yang sama dengan ORM)?
rwong
4
Choosing a class to represent a Person provides the immediate benefit of creating a statically-verifiable API... but that comes with the cost of limiting opportunities or increasing costs for change and reuse later on.Salah, dan sangat tidak jujur. Ini meningkatkan peluang Anda untuk berubah di kemudian hari, karena ketika Anda melakukan perubahan, kompiler akan secara otomatis menemukan dan menunjukkan kepada Anda setiap tempat yang perlu diperbarui untuk mempercepat seluruh basis kode Anda. Ada dalam kode dinamis, di mana Anda tidak bisa melakukan itu, bahwa Anda benar-benar digabungkan ke pilihan sebelumnya!
Mason Wheeler
4
@MasonWheeler: Apa yang sebenarnya Anda katakan adalah bahwa Anda menghargai keamanan tipe waktu kompilasi dibandingkan dengan struktur data yang lebih dinamis (dan lebih longgar).
Robert Harvey
1
Polimorfisme bukanlah konsep yang terbatas pada OOP. Dalam hal peta Anda mungkin memiliki polimorfisme inklusif (jika elemen-elemen tersebut adalah subtipe dari beberapa jenis yang dapat ditangani oleh peta) atau polimorfisme ad-hoc (jika elemen-elemen tersebut ditandai serikat pekerja). Ini internal. Operasi yang dapat dilakukan pada peta juga bisa bersifat polimorfik. Polimorfisme parametrik ketika kita menggunakan fungsi tingkat tinggi pada elemen atau ad-hoc saat pengiriman. Enkapsulasi dapat dicapai dengan ruang nama atau bentuk manajemen visibilitas lainnya. Pada dasarnya, isolasi objek tidak sama dengan menetapkan operasi ke tipe data.
siefca
1
@GillBates mengapa Anda mengatakan itu? Anda baru saja kehilangan kesempatan untuk meletakkan metode virtual itu "di dalam Peta" - tetapi itulah yang dibicarakan oleh Rich Hickey, "ActiveObjects" benar-benar anti-pola. Anda harus memperlakukan data seperti apa adanya (data), dan tidak menjalinnya dengan perilaku. Ada manfaat kesederhanaan yang sangat besar untuk dicapai dengan memisahkan masalah.
Virgil
4
  • Jika data memiliki sedikit atau tidak ada perilaku, dengan konten fleksibel yang cenderung berubah, gunakan Peta. IMO, tipikal "javabean" atau "Data Object" yang terdiri dari Model Domain Anemik dengan bidang N, N setter dan N getter, adalah buang-buang waktu. Jangan mencoba untuk mengesankan orang lain dengan struct Anda yang dimuliakan dengan membungkusnya dalam kelas smancy mewah. Jujurlah, jelaskan niat Anda , dan gunakan Peta. (Atau, jika masuk akal untuk domain Anda, JSON atau objek XML)

  • Jika data memiliki perilaku aktual yang signifikan, alias metode ( Katakan, Jangan Tanyakan ), maka gunakan kelas. Dan tepuk-tepuk diri Anda di belakang untuk menggunakan pemrograman Berorientasi Objek nyata :-).

  • Jika data memiliki banyak perilaku validasi esensial dan bidang yang diperlukan, gunakan kelas.

  • Jika data memiliki perilaku validasi dalam jumlah sedang, itu adalah garis batas.

  • Jika data memunculkan peristiwa perubahan properti, itu sebenarnya lebih mudah dan tidak terlalu membosankan dengan Peta. Cukup tulis subkelas kecil.

  • Salah satu kelemahan utama menggunakan Peta adalah bahwa pengguna harus memberikan nilai ke Strings, ints, Foos, dll. Jika ini sangat menjengkelkan dan rentan kesalahan, pertimbangkan kelas. Atau pertimbangkan kelas pembantu yang membungkus Peta dengan getter yang relevan.

pengguna949300
sumber
1
Sebenarnya, apa yang dikatakan Rich Hickey adalah bahwa Jika data memiliki perilaku aktual yang signifikan ... Anda mungkin melakukan kesalahan "desain" secara keseluruhan. Data adalah "informasi". Informasi, di dunia nyata BUKAN "tempat penyimpanan data". Informasi tidak memiliki "operasi yang mengendalikan bagaimana informasi berubah". Kami tidak menyampaikan informasi dengan memberi tahu orang-orang di mana itu disimpan. Metafora berorientasi objek adalah suatu model dunia yang tepat ... tetapi lebih sering tidak. Itu yang dia katakan - "pikirkan masalah ypur". Tidak semuanya adalah objek - beberapa hal.
Virgil
0

API untuk a mapmemiliki dua level.

  1. API untuk peta.
  2. Konvensi aplikasi.

API dapat dijelaskan di peta dengan konvensi. Misalnya pasangan :api api-validatebisa ditempatkan di peta atau :api-foo validate-foobisa juga konvensi. Peta bahkan dapat menyimpan api api-documentation-link.

Menggunakan konvensi memungkinkan programmer membuat bahasa khusus domain yang menstandarkan akses lintas "tipe" yang diimplementasikan sebagai peta. Menggunakan (keys map)memungkinkan untuk menentukan properti saat runtime.

Tidak ada yang ajaib pada peta dan tidak ada yang magis tentang objek. Ini semua pengiriman.

ben rudgers
sumber