Dapat berubah vs objek yang tidak berubah

173

Saya mencoba untuk membuat kepala saya berputar-putar vs benda yang tidak bisa diubah. Menggunakan objek yang bisa berubah mendapat banyak tekanan buruk (misalnya mengembalikan serangkaian string dari suatu metode) tapi saya mengalami kesulitan memahami apa dampak negatif dari ini. Apa praktik terbaik di sekitar menggunakan objek yang bisa berubah? Haruskah Anda menghindarinya sedapat mungkin?

Alex Angas
sumber
stringtidak berubah, setidaknya dalam. NET, dan saya pikir dalam banyak bahasa modern lainnya juga.
Domenic
4
itu tergantung pada apa String sebenarnya dalam bahasa - 'string' erlang hanyalah sebuah array int, dan haskell "string" adalah array chars.
Chii
4
String Ruby adalah array byte yang bisa berubah . Benar-benar membuat saya gila sehingga Anda dapat mengubahnya di tempat.
Daniel Spiewak
6
Daniel - mengapa? Apakah Anda menemukan diri Anda melakukannya secara tidak sengaja?
5
@DanielSpiewak Solusi untuk menjadi mur yang dikendalikan agar Anda dapat mengubah string di tempat itu sederhana: jangan lakukan itu. Solusi untuk menjadi gila karena Anda tidak dapat mengubah string di tempat tidak semudah itu.
Kaz

Jawaban:

162

Nah, ada beberapa aspek dalam hal ini.

  1. Objek yang bisa berubah tanpa referensi-identitas dapat menyebabkan bug di waktu ganjil. Misalnya, pertimbangkan Personkacang dengan equalsmetode berbasis nilai :

    Map<Person, String> map = ...
    Person p = new Person();
    map.put(p, "Hey, there!");
    
    p.setName("Daniel");
    map.get(p);       // => null
    

    Mesin Personvirtual akan "hilang" di peta ketika digunakan sebagai kunci karena sifatnya hashCodedan kesetaraannya didasarkan pada nilai yang bisa diubah. Nilai-nilai itu berubah di luar peta dan semua hashing menjadi usang. Para ahli teori suka mengomel tentang hal ini, tetapi dalam praktiknya saya belum menganggapnya sebagai masalah yang terlalu besar.

  2. Aspek lain adalah "kewajaran" logis dari kode Anda. Ini adalah istilah yang sulit untuk didefinisikan, mencakup segalanya mulai dari keterbacaan hingga mengalir. Secara umum, Anda harus dapat melihat sepotong kode dan dengan mudah memahami apa yang dilakukannya. Tetapi yang lebih penting dari itu, Anda harus bisa meyakinkan diri sendiri bahwa itu melakukan apa yang dilakukannya dengan benar . Ketika objek dapat berubah secara independen di berbagai kode "domain", terkadang menjadi sulit untuk melacak apa yang ada di mana dan mengapa (" aksi seram di kejauhan "). Ini adalah konsep yang lebih sulit untuk dicontohkan, tetapi ini adalah sesuatu yang sering dihadapi dalam arsitektur yang lebih besar dan lebih kompleks.

  3. Akhirnya, objek yang bisa berubah adalah pembunuh dalam situasi bersamaan. Setiap kali Anda mengakses objek yang bisa berubah dari utas terpisah, Anda harus berurusan dengan penguncian. Ini mengurangi throughput dan membuat kode Anda secara dramatis lebih sulit untuk dipelihara. Suatu sistem yang cukup rumit meniup masalah ini sejauh tidak proporsional sehingga menjadi hampir tidak mungkin untuk dipertahankan (bahkan untuk para ahli konkurensi).

Objek yang tidak dapat diubah (dan lebih khusus lagi, koleksi yang tidak dapat diubah) menghindari semua masalah ini. Setelah Anda memikirkan bagaimana cara kerjanya, kode Anda akan berkembang menjadi sesuatu yang lebih mudah dibaca, lebih mudah untuk dipelihara dan kecil kemungkinannya untuk gagal dengan cara yang aneh dan tidak terduga. Objek yang tidak dapat diubah bahkan lebih mudah untuk diuji, karena tidak hanya sifatnya yang mudah dipermainkan, tetapi juga pola kode yang cenderung ditegakkan. Singkatnya, mereka latihan yang baik di sekitar!

Dengan itu, saya hampir tidak fanatik dalam hal ini. Beberapa masalah tidak bisa dimodelkan dengan baik ketika semuanya tidak berubah. Tapi saya pikir Anda harus mencoba untuk mendorong sebanyak mungkin kode Anda ke arah itu, dengan asumsi tentu saja bahwa Anda menggunakan bahasa yang membuat ini pendapat yang dapat dipertahankan (C / C ++ membuat ini sangat sulit, seperti halnya Java) . Singkatnya: keuntungannya agak tergantung pada masalah Anda, tetapi saya cenderung lebih memilih kekekalan.

Daniel Spiewak
sumber
11
Respon yang bagus Namun satu pertanyaan kecil: bukankah C ++ memiliki dukungan yang baik untuk imutabilitas? Apakah fitur const-correctness tidak cukup?
Dimitri C.
1
@ DimitriC .: C ++ sebenarnya memiliki beberapa fitur yang lebih mendasar, terutama perbedaan yang lebih baik antara lokasi penyimpanan yang seharusnya merangkum keadaan tidak dibagi dari yang merangkum identitas objek.
supercat
2
Dalam hal pemrograman Java, Joshua Bloch membawa penjelasan yang bagus tentang topik ini dalam bukunya yang terkenal, Java Efektif (Item 15)
dMathieuD
27

Objek yang Tidak Berubah vs Koleksi yang Tidak Berubah

Salah satu poin penting dalam debat tentang objek yang dapat berubah dan tidak berubah adalah kemungkinan memperluas konsep keabadian ke koleksi. Objek yang tidak dapat diubah adalah objek yang sering mewakili struktur logis data tunggal (misalnya string yang tidak dapat diubah). Ketika Anda memiliki referensi ke objek abadi, konten objek tidak akan berubah.

Koleksi abadi adalah koleksi yang tidak pernah berubah.

Ketika saya melakukan operasi pada koleksi yang bisa diubah, maka saya mengubah koleksi di tempat, dan semua entitas yang memiliki referensi ke koleksi akan melihat perubahan.

Ketika saya melakukan operasi pada koleksi yang tidak dapat diubah, referensi dikembalikan ke koleksi baru yang mencerminkan perubahan. Semua entitas yang memiliki referensi ke versi koleksi sebelumnya tidak akan melihat perubahan.

Implementasi yang cerdik tidak perlu menyalin (mengkloning) seluruh koleksi untuk memberikan keabadian itu. Contoh paling sederhana adalah stack diimplementasikan sebagai daftar yang terhubung secara tunggal dan operasi push / pop. Anda dapat menggunakan kembali semua node dari koleksi sebelumnya di koleksi baru, menambahkan hanya satu node untuk push, dan kloning tidak ada node untuk pop. Operasi push_tail pada daftar yang terhubung sendiri, di sisi lain, tidak begitu sederhana atau efisien.

Variabel / referensi tidak berubah vs dapat diubah

Beberapa bahasa fungsional mengambil konsep immutability ke objek referensi sendiri, hanya memungkinkan penugasan referensi tunggal.

  • Di Erlang ini berlaku untuk semua "variabel". Saya hanya bisa menetapkan objek ke referensi satu kali. Jika saya mengoperasikan koleksi, saya tidak akan dapat menetapkan kembali koleksi baru ke referensi lama (nama variabel).
  • Scala juga membangun ini ke dalam bahasa dengan semua referensi dideklarasikan dengan var atau val , vals hanya menjadi penugasan tunggal dan mempromosikan gaya fungsional, tetapi vars memungkinkan struktur program yang lebih mirip C atau mirip Java.
  • Deklarasi var / val diperlukan, sementara banyak bahasa tradisional menggunakan pengubah opsional seperti final di java dan const di C.

Kemudahan Pengembangan vs. Kinerja

Hampir selalu alasan untuk menggunakan objek yang tidak dapat diubah adalah untuk mempromosikan pemrograman bebas efek samping dan alasan sederhana tentang kode (terutama dalam lingkungan paralel / sangat bersamaan). Anda tidak perlu khawatir tentang data dasar yang diubah oleh entitas lain jika objek tidak berubah.

Kelemahan utama adalah kinerja. Berikut ini adalah tulisan pada tes sederhana yang saya lakukan di Jawa membandingkan beberapa objek yang tidak berubah vs bisa berubah dalam masalah mainan.

Masalah kinerja diperdebatkan di banyak aplikasi, tetapi tidak semua, itulah sebabnya banyak paket numerik besar, seperti kelas Numpy Array di Python, memungkinkan pembaruan In-Place dari array besar. Ini akan menjadi penting untuk area aplikasi yang memanfaatkan operasi matriks dan vektor yang besar. Masalah paralel-data dan intensif komputasi yang besar ini mencapai kecepatan tinggi dengan beroperasi di tempat.

Ben Jackson
sumber
12

Periksa posting blog ini: http://www.yegor256.com/2014/06/09/objects-should-be-immutable.html . Ini menjelaskan mengapa objek tidak berubah lebih baik daripada bisa berubah. Pendeknya:

  • objek abadi lebih mudah untuk dibangun, diuji, dan digunakan
  • benda yang benar-benar abadi selalu aman dari utas
  • mereka membantu untuk menghindari penggabungan waktu
  • penggunaannya bebas efek samping (tidak ada salinan defensif)
  • masalah mutabilitas identitas dihindari
  • mereka selalu memiliki atomisitas kegagalan
  • mereka lebih mudah untuk di-cache
yegor256
sumber
10

Benda yang tidak dapat berubah adalah konsep yang sangat kuat. Mereka menghilangkan banyak beban untuk mencoba menjaga objek / variabel tetap konsisten untuk semua klien.

Anda bisa menggunakannya untuk objek non-polimorfik tingkat rendah - seperti kelas CPoint - yang sebagian besar digunakan dengan semantik nilai.

Atau Anda dapat menggunakannya untuk level tinggi, antarmuka polimorfik - seperti IFunction yang mewakili fungsi matematika - yang digunakan secara khusus dengan semantik objek.

Keuntungan terbesar: imutabilitas + semantik objek + pointer pintar menjadikan kepemilikan objek tidak menjadi masalah, semua klien objek memiliki salinan pribadi mereka secara default. Secara implisit ini juga berarti perilaku deterministik di hadapan konkurensi.

Kerugian: ketika digunakan dengan objek yang berisi banyak data, konsumsi memori dapat menjadi masalah. Solusi untuk ini bisa dengan menjaga operasi pada objek simbolis dan melakukan evaluasi malas. Namun, ini kemudian dapat mengarah pada rantai perhitungan simbolik, yang dapat memengaruhi kinerja secara negatif jika antarmuka tidak dirancang untuk mengakomodasi operasi simbolik. Sesuatu yang pasti harus dihindari dalam hal ini adalah mengembalikan potongan memori besar dari suatu metode. Dalam kombinasi dengan operasi simbolik berantai, ini dapat menyebabkan konsumsi memori yang besar dan penurunan kinerja.

Jadi objek yang tidak berubah jelas merupakan cara utama saya untuk berpikir tentang desain berorientasi objek, tetapi mereka bukan dogma. Mereka memecahkan banyak masalah untuk klien objek, tetapi juga membuat banyak, terutama untuk para pelaksana.

QBziZ
sumber
Saya pikir saya salah memahami Bagian 4 untuk keuntungan terbesar: immutability + objek semantik + smart pointer menjadikan kepemilikan objek sebagai "moot" point. Jadi ini masih bisa diperdebatkan? Saya pikir Anda menggunakan "moot" secara tidak benar ... Melihat kalimat berikutnya adalah objek tersebut menyiratkan "perilaku deterministik" dari perilaku "moot" (dapat diperdebatkan).
Benjamin
Anda benar, saya salah menggunakan 'moot'. Pertimbangkan itu berubah :)
QBziZ
6

Anda harus menentukan bahasa apa yang Anda bicarakan. Untuk bahasa tingkat rendah seperti C atau C ++, saya lebih suka menggunakan objek yang bisa berubah untuk menghemat ruang dan mengurangi churn memori. Dalam bahasa tingkat yang lebih tinggi, objek yang tidak dapat diubah membuatnya lebih mudah untuk mempertimbangkan perilaku kode (terutama kode multi-utas) karena tidak ada "aksi seram di kejauhan".

John Millikin
sumber
Anda menyarankan agar utas terjerat secara kuantitatif? Itu cukup berat :) Utas sebenarnya, jika Anda pikirkan, cukup dekat untuk terjerat. Perubahan yang dilakukan salah satu thread memengaruhi yang lain. +1
ndrewxie
4

Objek yang bisa berubah hanyalah sebuah objek yang dapat dimodifikasi setelah dibuat / dipakai, vs objek yang tidak dapat diubah yang tidak dapat dimodifikasi (lihat halaman Wikipedia pada subjek). Contoh dari ini dalam bahasa pemrograman adalah daftar dan tupel Python. Daftar dapat dimodifikasi (misalnya, item baru dapat ditambahkan setelah dibuat) sedangkan tupel tidak bisa.

Saya tidak benar-benar berpikir ada jawaban yang jelas mengenai mana yang lebih baik untuk semua situasi. Mereka berdua memiliki tempat masing-masing.

Willurd
sumber
1

Jika tipe kelas bisa berubah, variabel dari tipe kelas itu dapat memiliki sejumlah arti yang berbeda. Sebagai contoh, anggaplah suatu objek foomemiliki bidang int[] arr, dan itu memegang referensi ke int[3]holding angka {5, 7, 9}. Meskipun jenis bidangnya diketahui, setidaknya ada empat hal berbeda yang dapat diwakilinya:

  • Referensi yang berpotensi dibagikan, semua pemiliknya hanya peduli bahwa itu merangkum nilai 5, 7, dan 9. Jika fooingin arrmerangkum nilai yang berbeda, ia harus menggantinya dengan array berbeda yang berisi nilai yang diinginkan. Jika seseorang ingin membuat salinanfoo , seseorang dapat memberikan salinan referensi arratau array baru yang menyimpan nilai {1,2,3}, mana yang lebih nyaman.

  • Satu-satunya referensi, di mana saja di alam semesta, ke array yang merangkum nilai 5, 7, dan 9. set dari tiga lokasi penyimpanan yang saat ini memegang nilai 5, 7, dan 9; jika fooingin mengenkapsulasi nilai 5, 8, dan 9, itu dapat mengubah item kedua dalam array itu atau membuat array baru yang menyimpan nilai 5, 8, dan 9 dan meninggalkan yang lama. Perhatikan bahwa jika seseorang ingin membuat salinan foo, orang harus menyalin mengganti arrdengan referensi ke array baru agarfoo.arr tetap sebagai satu-satunya referensi ke array itu di mana saja di alam semesta.

  • Referensi ke array yang dimiliki oleh beberapa objek lain yang telah mengeksposnya foountuk beberapa alasan (misalnya mungkin ingin foomenyimpan beberapa data di sana). Dalam skenario ini, arrtidak merangkum konten array, melainkan identitasnya . Karena mengganti arrdengan referensi ke array baru akan benar-benar mengubah maknanya, salinan fooharus memiliki referensi ke array yang sama.

  • Referensi ke array yang foomerupakan pemilik tunggal, tetapi referensi yang dipegang oleh objek lain untuk beberapa alasan (misalnya ia ingin memiliki objek lain untuk menyimpan data di sana - sisi sebaliknya dari kasus sebelumnya). Dalam skenario ini, arrmerangkum identitas array dan kontennya. Mengganti arrdengan referensi ke array baru akan benar-benar mengubah maknanya, tetapi memiliki clone yang arrmengacu foo.arrakan melanggar asumsi bahwa fooadalah pemilik tunggal. Dengan demikian tidak ada cara untuk menyalin foo.

Secara teori, int[]harus menjadi tipe sederhana yang didefinisikan dengan baik, tetapi memiliki empat arti yang sangat berbeda. Sebaliknya, referensi ke objek yang tidak dapat diubah (mis. String) Umumnya hanya memiliki satu makna. Sebagian besar "kekuatan" benda yang tidak dapat diubah berasal dari fakta itu.

supercat
sumber
1

Mesin virtual yang dapat diubah dilewatkan dengan referensi.

Instance yang tidak dapat diubah diteruskan oleh nilai.

Contoh abstrak. Misalkan ada file bernama txtfile di HDD saya. Sekarang, ketika Anda meminta txtfile dari saya, saya dapat mengembalikannya dalam dua mode:

  1. Buat pintasan ke txtfile dan paskan pintasan untuk Anda, atau
  2. Ambil salinan untuk txtfile dan paskan salin kepada Anda.

Dalam mode pertama, txtfile yang dikembalikan adalah file yang dapat diubah, karena ketika Anda melakukan perubahan pada file pintasan, Anda juga melakukan perubahan pada file asli. Kelebihan dari mode ini adalah bahwa setiap pintasan yang dikembalikan memerlukan lebih sedikit memori (pada RAM atau dalam HDD) dan kerugiannya adalah setiap orang (tidak hanya saya, pemilik) memiliki izin untuk mengubah konten file.

Dalam mode kedua, txtfile yang dikembalikan adalah file yang tidak dapat diubah, karena semua perubahan pada file yang diterima tidak merujuk ke file asli. Kelebihan dari mode ini adalah hanya saya (pemilik) yang dapat memodifikasi file asli dan kelemahannya adalah setiap salinan yang dikembalikan membutuhkan memori (dalam RAM atau dalam HDD).

Teodor
sumber
Ini tidak sepenuhnya benar. Contoh yang tidak dapat diubah memang bisa dilewatkan dengan referensi, dan seringkali memang demikian. Menyalin terutama dilakukan ketika Anda perlu memperbarui objek atau struktur data.
Jamon Holmgren
0

Jika Anda mengembalikan referensi array atau string, maka dunia luar dapat memodifikasi konten dalam objek itu, dan karenanya menjadikannya sebagai objek yang dapat diubah (dapat dimodifikasi).

pengguna1923551
sumber
0

Berarti abadi tidak bisa diubah, dan bisa berubah berarti Anda bisa berubah.

Objek berbeda dari primitif di Jawa. Primitif dibangun dalam tipe (boolean, int, dll) dan objek (kelas) adalah tipe yang dibuat pengguna.

Primitif dan objek dapat berubah atau tidak berubah ketika didefinisikan sebagai variabel anggota dalam implementasi kelas.

Banyak orang berpikir primitif dan variabel objek memiliki pengubah akhir di depan mereka tidak berubah, namun, ini tidak sepenuhnya benar. Jadi akhir hampir tidak berarti tidak berubah untuk variabel. Lihat contoh di sini
http://www.siteconsortium.com/h/D0000F.php .

JTHouseCat
sumber
0

Immutable object- adalah keadaan objek yang tidak dapat diubah setelah pembuatan. Objek tidak berubah ketika semua bidangnya tidak berubah

Aman utas

Keuntungan utama dari objek yang tidak dapat diubah adalah bahwa benda itu alami untuk lingkungan bersamaan. Masalah terbesar dalam konkurensi adalah shared resourceyang dapat diubah setiap utas. Tetapi jika suatu objek tidak berubah itu adalah read-onlyoperasi yang aman thread. Setiap modifikasi dari objek abadi asli mengembalikan salinan

efek samping gratis

Sebagai pengembang, Anda sepenuhnya yakin bahwa keadaan objek yang tidak dapat diubah tidak dapat diubah dari tempat mana pun (sengaja atau tidak)

kompilasi optimasi

Meningkatkan kinerja

Kerugian:

Menyalin objek adalah operasi yang lebih berat daripada mengubah objek yang bisa berubah, itulah sebabnya ia memiliki beberapa jejak kinerja

Untuk membuat immutableobjek, Anda harus menggunakan:

  1. Tingkat bahasa Setiap bahasa berisi alat untuk membantu Anda. Misalnya Java memiliki finaldan primitives, Swift memiliki letdan struct[Tentang] . Bahasa mendefinisikan jenis variabel. Misalnya Java memiliki primitivedan referencemengetik, Swift memiliki valuedan referencemengetik [Tentang] . Untuk objek yang tidak dapat diubah, lebih nyaman adalah primitivesdan valueketik yang membuat salinan secara default. Adapun referencejenis itu lebih sulit (karena Anda dapat mengubah keadaan objek dari itu) tetapi mungkin. Misalnya Anda dapat menggunakan clonepola di tingkat pengembang

  2. Tingkat pengembang. Sebagai pengembang, Anda tidak harus menyediakan antarmuka untuk perubahan status

yoAlex5
sumber