Değişken ve değişmeyen nesneler


173

Başımı değişmez ve değişmez nesnelerin etrafına döndürmeye çalışıyorum. Değişken nesneler kullanmak çok fazla kötü baskı alır (örneğin, bir yöntemden dizeler dizisi döndürme), ancak bunun olumsuz etkilerinin ne olduğunu anlamada sorun yaşıyorum. Değişken nesneleri kullanma konusunda en iyi uygulamalar nelerdir? Mümkün olduğunca onlardan kaçınmalı mısınız?


stringdeğişmez, en azından .NET ve ben de diğer birçok modern dilde düşünüyorum.
Domenic

4
bir Dize'nin gerçekte dilde ne olduğuna bağlıdır - erlang 'dizeleri' sadece bir dizi dizisidir ve haskell "dizeleri" karakter dizileridir.
Chii

4
Ruby dizeleri değiştirilebilir bir bayt dizisidir. Kesinlikle beni yerinde değiştirebileceğiniz fındık kullanıyor.
Daniel Spiewak

6
Daniel - neden? Kendinizi kazara yapıyor musunuz?

5
@DanielSpiewak Dizeleri yerinde değiştirebileceğiniz somun tahrikinin çözümü basittir: Yapmayın. Nedeniyle tahrik fındık olduğu için çözüm olamaz yerde dizeleri değiştirmek basit bu değil.
Kaz

Yanıtlar:


162

Bunun birkaç yönü var.

  1. Referans kimliği olmayan değiştirilebilir nesneler tuhaf zamanlarda hatalara neden olabilir. Örneğin, Persondeğere dayalı bir equalsyöntemi olan bir fasulyeyi düşünün :

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

    PersonOnun yüzünden bir anahtar olarak kullanıldığında örnek haritası "kayıp" alır hashCodeve eşitlik değişken değerlere dayalı bulundu. Bu değerler haritanın dışında değişti ve tüm karmalar eskimiş oldu. Kuramcılar bu noktada arpayı severler, ancak pratikte bunun çok fazla bir sorun olduğunu bulamadım.

  2. Başka bir yönü, kodunuzun mantıksal "makul" oluşudur. Bu, okunabilirlikten akışa kadar her şeyi kapsayan, tanımlanması zor bir terimdir. Genel olarak, bir parça koda bakabilmeli ve ne yaptığını kolayca anlayabilmelisiniz. Ancak bundan daha önemli olan, kendinizi doğru şekilde yaptığı şeyi ikna edebilmeniz gerekir . Nesneler, farklı kod "etki alanları" arasında bağımsız olarak değiştiğinde, nerede ve neden olduğunu (" uzaktan ürkütücü eylem ") izlemek bazen zorlaşır . Bu örneklenmesi daha zor bir kavramdır, ancak daha büyük, daha karmaşık mimarilerde sıklıkla karşılaşılan bir şeydir.

  3. Son olarak, değişken nesneler eşzamanlı durumlarda katildir . Ayrı iş parçacıklarından değiştirilebilir bir nesneye her eriştiğinizde, kilitleme ile uğraşmanız gerekir. Bu, verimi azaltır ve kodunuzun bakımını önemli ölçüde zorlaştırır. Yeterince karmaşık bir sistem bu sorunu o kadar ileriye götürür ki, sürdürülmesi neredeyse imkansız hale gelir (eşzamanlılık uzmanları için bile).

Değişmez nesneler (ve özellikle değişmez koleksiyonlar) tüm bu problemlerden kaçınır. Nasıl çalıştıklarına karar verdiğinizde, kodunuz okunması daha kolay, bakımı daha kolay ve garip ve öngörülemeyen şekillerde başarısız olma olasılığı daha düşük bir şeye dönüşecektir. Değişmez nesnelerin test edilmesi daha kolaydır, sadece kolay takılabilirliklerinden değil, aynı zamanda zorlama eğiliminde oldukları kod kalıplarından dolayı. Kısacası, her yerde iyi bir uygulama!

Bununla birlikte, bu konuda neredeyse bir gayretim var. Bazı sorunlar, her şey değişmez olduğunda güzel bir şekilde modellenmez. Ama elbette bunu kabul edilebilir bir görüş haline getiren bir dil kullandığınızı varsayarak, kodunuzu mümkün olduğunca bu yönde itmeye çalışmanız gerektiğini düşünüyorum (C / C ++ bunu Java gibi zorlaştırıyor) . Kısacası: avantajlar sizin probleminize bağlıdır, ancak değişmezliği tercih etme eğilimindeyim.


11
Harika yanıt. Ancak küçük bir soru: C ++ değişmezliği iyi desteklemiyor mu? Is not const-doğruluğu özelliği yeterli?
Dimitri

1
@DimitriC .: C ++ aslında daha temel özelliklere sahiptir, en önemlisi, paylaşılmayan durumu nesne kimliğini kapsülleyenlerden kapsadığı varsayılan depolama konumları arasında daha iyi bir ayrımdır.
supercat

2
Java programlama durumunda Joshua Bloch, ünlü kitabı Effective Java (Item 15)
dMathieuD

27

Değişmez Nesneler ve Değişmez Koleksiyonlar

Değişken ve değişmez nesneler üzerindeki tartışmadaki en ince noktalardan biri değişmezlik kavramını koleksiyonlara genişletme olasılığıdır. Değişmez bir nesne, genellikle verilerin tek bir mantıksal yapısını (örneğin değişmez bir dize) temsil eden bir nesnedir. Değişmez bir nesneye referansınız olduğunda, nesnenin içeriği değişmez.

Değişmez bir koleksiyon asla değişmeyen bir koleksiyon.

Değişken bir koleksiyon üzerinde bir işlem gerçekleştirdiğimde, koleksiyonu yerinde değiştiriyorum ve koleksiyona referansları olan tüm varlıklar değişikliği görecek.

Değişmez bir koleksiyon üzerinde bir işlem gerçekleştirdiğimde, değişikliği yansıtan yeni bir koleksiyona bir başvuru döndürülür. Koleksiyonun önceki sürümlerine referans veren tüm varlıklar değişikliği görmez.

Akıllı uygulamaların, bu değişmezliği sağlamak için tüm koleksiyonu kopyalaması (klonlaması) gerekmez. En basit örnek, tek bağlantılı bir liste olarak uygulanan yığın ve push / pop işlemleri. Yeni koleksiyondaki önceki koleksiyondaki tüm düğümleri, itme için yalnızca tek bir düğüm ekleyerek ve pop için hiçbir düğümü klonlayarak yeniden kullanabilirsiniz. Diğer taraftan, tekil bağlantılı bir listedeki push_tail işlemi o kadar basit veya verimli değildir.

Değişmez - Değişken Değişkenler / Referanslar

Bazı fonksiyonel diller, nesne referanslarına değişmezlik kavramını alarak sadece tek bir referans atamasına izin verir.

  • Erlang'da bu tüm "değişkenler" için geçerlidir. Bir referansa yalnızca bir kez nesne atayabilirim. Bir koleksiyon üzerinde çalışacak olsaydım, yeni koleksiyonu eski referansa (değişken adı) yeniden atayamazdım.
  • Scala ayrıca bunu var veya val ile bildirilen tüm referanslarla dile dönüştürür , vals sadece tek bir atamadır ve fonksiyonel bir tarzı teşvik eder, ancak daha C benzeri veya Java benzeri bir program yapısına izin verir.
  • Var / val bildirimi gerekirken, birçok geleneksel dil java'da final ve C'de const gibi isteğe bağlı değiştiriciler kullanır .

Gelişim Kolaylığı ve Performans

Hemen hemen her zaman değişmez bir nesne kullanmanın nedeni, yan etkisi olmayan programlamayı ve kod hakkında basit akıl yürütmeyi (özellikle yüksek eşzamanlı / paralel bir ortamda) teşvik etmektir. Nesne değişmezse, temel verilerin başka bir varlık tarafından değiştirilmesinden endişelenmenize gerek yoktur.

Ana dezavantajı performanstır. İşte Java'da yaptığım basit bir test üzerine bir oyuncak problemindeki değişmez ve değişmez nesneleri karşılaştıran bir yazı .

Performans sorunları birçok uygulamada tartışmalı, ancak hepsi değil, bu yüzden Python'daki Numpy Array sınıfı gibi birçok büyük sayısal paket büyük dizilerin Yerinde güncellemelerine izin verir. Bu, büyük matris ve vektör işlemlerini kullanan uygulama alanları için önemli olacaktır. Bu büyük veri paralel ve hesaplamalı olarak yoğun problemler yerinde çalışarak büyük bir hızlanma sağlar.


12

Bu blog gönderisini kontrol edin: http://www.yegor256.com/2014/06/09/objects-should-be-immutable.html . Değişmez cisimlerin neden değiştirilenden daha iyi olduğunu açıklar. Kısacası:

  • değişmez nesnelerin oluşturulması, test edilmesi ve kullanılması daha kolaydır
  • gerçekten değişmeyen nesneler her zaman iş parçacığı için güvenlidir
  • zamansal bağlantıdan kaçınmaya yardımcı olurlar
  • kullanımları yan etkisiz (defansif kopya yok)
  • kimlik değişebilirliği probleminden kaçınılır
  • her zaman başarısız atomikliğe sahiptirler
  • önbelleğe almak çok daha kolay

10

Değişmez nesneler çok güçlü bir kavramdır. Nesneleri / değişkenleri tüm istemciler için tutarlı tutmaya çalışmanın yükünü ortadan kaldırırlar.

Bunları, çoğunlukla değer semantiği ile kullanılan düşük seviyeli, polimorfik olmayan nesneler (bir CPoint sınıfı gibi) için kullanabilirsiniz.

Veya bunları yalnızca nesne semantiği ile kullanılan, bir matematik fonksiyonunu temsil eden bir EĞER işlevi gibi, üst düzey, polimorfik arabirimler için kullanabilirsiniz.

En büyük avantaj: değişmezlik + nesne semantiği + akıllı işaretçiler nesne sahipliğini bir sorun haline getirir, nesnenin tüm istemcileri varsayılan olarak kendi özel kopyalarına sahiptir. Örtük olarak bu aynı zamanda eşzamanlılık mevcudiyetinde deterministik davranış anlamına da gelir.

Dezavantajı: Çok fazla veri içeren nesnelerle kullanıldığında, bellek tüketimi sorun haline gelebilir. Buna bir çözüm, bir nesne üzerindeki işlemleri sembolik tutmak ve tembel bir değerlendirme yapmak olabilir. Bununla birlikte, bu daha sonra arayüz sembolik işlemleri içerecek şekilde tasarlanmamışsa performansı olumsuz etkileyebilecek sembolik hesaplama zincirlerine yol açabilir. Bu durumda kesinlikle kaçınacak bir şey, bir yöntemden büyük bellek parçaları döndürmektir. Zincirli sembolik işlemlerle birlikte, bu büyük bellek tüketimine ve performans düşüşüne neden olabilir.

Bu yüzden değişmez nesneler kesinlikle nesne yönelimli tasarım hakkında temel düşünme şeklimdir, ancak bunlar bir dogma değildir. Nesnelerin müşterileri için birçok sorunu çözüyorlar, ancak özellikle uygulayıcılar için de birçok sorun yaratıyorlar.


Bölüm 4'ü en büyük avantaj için yanlış anladığımı düşünüyorum: değişmezlik + nesne semantiği + akıllı işaretçiler nesne sahipliğini bir "tartışma" noktası haline getiriyor. Yani tartışmalı mı? Sanırım yanlış "moot" kullanıyorsunuz ... Bir sonraki cümle olarak görmek nesne "moot" (tartışmalı) davranış "deterministik davranış" ima etti.
Benjamin

Haklısın, 'moot'u yanlış kullandım.
Değiştiğini

6

Hangi dilde konuştuğunuzu belirtmelisiniz. C veya C ++ gibi düşük düzeyli diller için, alanı korumak ve bellek karmaşasını azaltmak için değiştirilebilir nesneler kullanmayı tercih ederim. Daha üst düzey dillerde, sabit nesneler, kodun davranışı (özellikle çok iş parçacıklı kod) hakkında mantık yürütmeyi kolaylaştırır, çünkü "uzaktan ürkütücü eylem" yoktur.


İpliklerin kuantum dolaşmış olmasını mı öneriyorsunuz? Bu oldukça gergin :) Konu aslında, eğer düşünmek, dolaşmış oldukça yakın. Bir iş parçacığının yaptığı bir değişiklik diğerini etkiler. +1
ndrewxie

4

Değiştirilebilir bir nesne, değiştirilemeyen / somutlaştırıldıktan sonra değiştirilemeyen, değiştirilemeyen değişmez bir nesneye göre değiştirilebilen bir nesnedir ( konuyla ilgili Wikipedia sayfasına bakın ). Programlama dilinde buna bir örnek Pythons listeleri ve tuples'tir. Listeler değiştirilebilir (örn. Oluşturulduktan sonra yeni öğeler eklenebilir), ancak tuples olamaz.

Gerçekten tüm durumlar için hangisinin daha iyi olduğuna dair kesin bir cevap olduğunu düşünmüyorum. İkisinin de yeri var.


1

Bir sınıf türü değiştirilebilirse, bu sınıf türünün bir değişkeni birkaç farklı anlama sahip olabilir. Örneğin, bir nesnenin foobir alanı olduğunu int[] arrve int[3]{5, 7, 9} sayılarını tutan bir referansa sahip olduğunu varsayalım . Alanın türü bilinmesine rağmen, temsil edebileceği en az dört farklı şey vardır:

  • Tüm sahipleri yalnızca 5, 7 ve 9 değerlerini kapsadığını önemseyen, potansiyel olarak paylaşılan bir başvuru. Farklı değerleri kapsüllemek fooistiyorsa arr, bunu istenen değerleri içeren farklı bir diziyle değiştirmelidir. Biri bir kopyasını oluşturmak istiyorsa foo, kopyaya ya bir referans arrya da {1,2,3} değerlerini içeren yeni bir dizi verebilir ( hangisi daha uygunsa).

  • Evrenin herhangi bir yerindeki 5, 7 ve 9 değerlerini kapsayan bir diziye tek başvuru, şu anda 5, 7 ve 9 değerlerini tutan üç depolama konumu kümesi; Eğer foobu değer 5, 8, ve 9 kapsüllemek isteyen, ya bu dizi ikinci madde değiştirmek veya değerleri 5, 8 tutma yeni bir dizi oluşturmak ve 9 ve eskisini terk edebilir. Birinin kopyasını çıkarmak isterse , evrenin herhangi bir yerinde bu diziye tek referans olarak kalabilmek için foo, kopyada arryeni bir diziye başvuru ile değiştirilmelidir foo.arr.

  • Bir nedenden dolayı onu ortaya çıkaran başka bir nesnenin sahip olduğu bir diziye yapılan başvuru foo(örneğin, foobazı verileri orada saklamak istiyor olabilir). Bu senaryoda, arrdizinin içeriğini değil, kimliğini kapsamaktadır . arrYeni bir diziye yapılan bir başvurunun yerine geçmesi anlamını tamamen değiştireceğinden, bir kopyası fooaynı diziye bir başvuru içermelidir.

  • Dizinin footek sahibi olan, ancak referansların başka bir nesne tarafından bir nedenden dolayı tutulduğu bir diziye yapılan başvuru (örneğin, diğer nesnenin verileri orada depolaması - önceki durumun tersi). Bu senaryoda, arrdizinin kimliğini ve içeriğini içerir. Yerine arrtamamen anlamını değiştirecek yeni bir diziye atıfta bulunarak, ama en klon sahip arratıfta foo.arrvarsayım ihlal edecek footek sahibidir. Dolayısıyla kopyalamanın bir yolu yoktur foo.

Teorik olarak, int[]iyi tanımlanmış hoş bir tür olmalı, ancak dört farklı anlamı vardır. Aksine, değişmez bir nesneye (örneğin String) yapılan bir referans genellikle sadece bir anlama sahiptir. Değişmez cisimlerin “gücünün” çoğu bu olgudan kaynaklanır.


1

Değişebilir örnekler referans ile geçirilir.

Değişmez örnekler değere göre geçirilir.

Soyut bir örnek. Sabit diskimde txtfile adında bir dosya olduğunu varsayalım . Şimdi, btffile benden sorduğunuzda , iki modda döndürebilirim:

  1. Size txtfile ve pas kısayolu için bir kısayol oluşturun veya
  2. Txtfile için bir kopya alın ve size pas kopyalayın.

İlk modda, döndürülen txtfile değiştirilebilir bir dosyadır, çünkü kısayol dosyasında değişiklik yaptığınızda, orijinal dosyada da değişiklikler yaparsınız. Bu modun avantajı, döndürülen her kısayolun (RAM veya HDD'de) daha az bellek gerektirmesidir ve dezavantajı, herkesin (sadece ben değil, sahip) dosya içeriğini değiştirme izinlerine sahip olmasıdır.

İkinci modda, döndürülen txtfile değişmez bir dosyadır, çünkü alınan dosyadaki tüm değişiklikler orijinal dosyaya başvurmaz. Bu modun avantajı, yalnızca ben (sahip) orijinal dosyayı değiştirebilmesidir ve dezavantajı, geri gönderilen her kopyanın bellek (RAM veya HDD'de) gerektirmesidir.


Bu kesinlikle doğru değil. Değişmez örnekler gerçekten referans olarak geçilebilir ve sıklıkla geçirilir. Kopyalama genellikle nesne veya veri yapısını güncellemeniz gerektiğinde yapılır.
Jamon Holmgren

0

Bir dizinin veya dizenin referanslarını döndürürseniz, dış dünya o nesnedeki içeriği değiştirebilir ve dolayısıyla onu değiştirilebilir (değiştirilebilir) nesne yapabilir.


0

Değişmez araçlar değiştirilemez ve değişebilir araçlar değiştirebileceğiniz anlamına gelir.

Nesneler Java'daki ilkellerden farklıdır. Temel öğeler türlerde (boolean, int vb.) Oluşturulur ve nesneler (sınıflar) kullanıcı tarafından oluşturulan türlerdir.

Bir sınıfın uygulanması içinde üye değişkenler olarak tanımlandığında ilkel öğeler ve nesneler değişebilir veya değişmez olabilir.

Birçok insan, önlerinde son bir değiştiriciye sahip olan ilkel ve nesne değişkenlerinin değişmez olduğunu düşünüyor, ancak bu tam olarak doğru değil. Yani final neredeyse değişkenler için değişmez anlamına gelmez. Buradaki örneğe bakın
http://www.siteconsortium.com/h/D0000F.php .


0

Immutable object- oluşturma işleminden sonra değiştirilemeyen bir nesne durumudur. Tüm alanları değiştirilemediğinde nesne değişmez

İplik kasası

Değişmez nesnenin ana avantajı, doğal olarak eşzamanlı çevre için olmasıdır. Eşzamanlılıktaki en büyük sorun, shared resourceherhangi bir iş parçacığının değiştirilebilmesidir. Fakat eğer bir nesne değişmezse, read-onlybu işlem güvenli bir işlemdir. Orijinal değiştirilemez nesnede yapılan herhangi bir değişiklik bir kopyasını döndürür

yan etkileri ücretsiz

Bir geliştirici olarak, değişmez nesnenin durumunun herhangi bir yerden değiştirilemeyeceğinden tamamen emin olabilirsiniz (bilerek veya değil)

optimizasyonu derle

Performans geliştirme

dezavantajı:

Nesnenin kopyalanması, değiştirilebilir bir nesneyi değiştirmekten daha ağır bir işlemdir, bu yüzden performansta bir miktar yer kaplar

Bir immutablenesne oluşturmak için şunları kullanmalısınız:

  1. Dil seviyesi. Her dil size yardımcı olacak araçlar içerir. Örneğin Java'da finalve primitives, Swift'de letve struct[Hakkında] vardır . Dil, bir değişken türünü tanımlar. Örneğin Java sahiptir primitiveve referencetipi, Swift sahiptir valueve referencetipi [Hakkında] . Değişmez nesneler için daha uygun olanıdır primitivesve valuevarsayılan olarak bir kopya yapan türdür. Gelince referenceancak olası (bunun nesnenin durumunu dışarı değiştirmek mümkün olduğu için) türü daha zordur. Örneğin, clonedeseni geliştirici düzeyinde kullanabilirsiniz

  2. Geliştirici seviyesi. Bir geliştirici olarak durumu değiştirmek için bir arayüz sağlamamalısınız

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.