Bir veri nesnesi türünün değişmeyecek şekilde tasarlanıp tasarlanmayacağına nasıl karar verilir?


23

Değişmez "kalıbı" güçlü olduğu için seviyorum ve geçmişte değişmez veri türlerine sahip sistemleri tasarlamayı yararlı buldum (bazıları, hatta hepsi). Sık sık yaptığım zaman kendimi daha az hata yazarken buluyorum ve hata ayıklama çok daha kolay.

Ancak, genel olarak akranlarım değişmez olmaktan uzak duruyor. Hiç deneyimsiz değiller (ondan uzak), ancak veri nesnelerini klasik yoldan yazıyorlar - özel üyeler bir alıcı ve her üye için belirleyici. O zaman genellikle kurucuları hiçbir argüman almazlar ya da sadece kolaylık sağlamak için bazı argümanlar alırlar. Sıklıkla, bir nesne oluşturmak şöyle görünür:

Foo a = new Foo();
a.setProperty1("asdf");
a.setProperty2("bcde");

Belki bunu her yerde yaparlar. Belki ne kadar önemli olursa olsun, bu iki ipi alan bir kurucu bile tanımlamazlar. Belki de daha sonra bu tellerin değerini değiştirmezler ve asla gerek duymazlar. Açıkçası, eğer bu şeyler doğruysa, nesne değişmez olarak tasarlanmalı, değil mi? (yapıcı iki özelliği alır, ayarlayıcı yok).

Bir nesne türünün değişmez olarak tasarlanıp tasarlanmayacağına nasıl karar veriyorsunuz? Yargılamak için iyi bir kriter seti var mı?

Şu anda kendi projemdeki birkaç veri türünün değişmez hale getirilip değiştirilmeyeceğini tartışıyorum, ancak bunu akranlarıma doğrulamak zorunda kalacağım ve türlerdeki veriler (ÇOK nadiren) değişebilir - tabii ki istediğiniz zaman değiştirebilirsiniz değişmez yolu (değiştirmek istediğinizler hariç eski nesneden özellikleri kopyalayarak, yeni bir tane oluşturun). Fakat bunun sadece ortaya çıkan değişmezlere olan aşkım olup olmadığından veya onlardan yararlanma / fayda için gerçek bir ihtiyaç olup olmadığından emin değilim.


1
Erlang'da Program ve tüm sorun çözüldü, her şey değişmez
Zachary K

1
@ZacharyK Aslında, işlevsel programlama hakkında bir şeyden bahsetmeyi düşünüyordum, ancak işlevsel programlama dilleri konusundaki sınırlı deneyimimden kaçındım.
Ricket,

Yanıtlar:


27

Geriye yaklaşıyor gibisin. Biri varsayılan olarak değişmez olmalıdır. Bir nesneyi yalnızca, kesinlikle değişmez bir nesne olarak çalışmasını sağlayamazsanız / yapamazsanız değiştirilebilir hale getirin.


11

Değişmez nesnelerin birincil yararı iplik güvenliğini garanti etmektir. Birden fazla çekirdek ve ipliğin norm olduğu bir dünyada, bu fayda çok önemli hale geldi.

Ancak değişken nesneler kullanmak çok uygundur. İyi performans sergiliyorlar ve onları ayrı dişlilerden değiştiremediğiniz sürece ve ne yaptığınızı iyi anlıyorsanız, oldukça güvenilirler.


15
Değiştirilemeyen nesnelerin yararı, sebep olmalarının kolay olmasıdır. Çok iş parçacıklı ortamda, bu son derece kolaydır; düz tek iş parçacıklı algoritmalarda hala daha kolaydır. Örneğin hiçbir zaman devletin tutarlı olup olmadığına dikkat etmek zorunda kalmazsınız, nesne belirli bir bağlamda kullanılması gereken tüm mutasyonları geçmedi mi, vb.
9000

-1, @ 9000 ile aynı fikirdeyim. İplik güvenliği ikincildir (değişmez görünen fakat örneğin memoizasyon nedeniyle dahili değişebilen durumu olan nesneleri düşünün). Ayrıca, değişken performansın arttırılması erken optimizasyondur ve kullanıcının "ne yaptığını bilmesini" gerektiriyorsa bir şeyi savunmak mümkündür. Her zaman ne yaptığımı bilseydim, asla hata içeren bir program yazmazdım.
Doval,

4
@Doval: Anlaşamayacak bir şey yok. 9000 kesinlikle doğru; Değişken olmayan objeler vardır ilgili nedenle daha kolay. Bu kısmen eşzamanlı programlama için onları çok kullanışlı kılan şeydir. Diğer kısım, devlet değişikliği konusunda endişelenmenize gerek olmamasıdır. Erken optimizasyon burada önemli değil; değişmez nesnelerin kullanımı performansla değil tasarımla ilgilidir ve öndeki veri yapılarının zayıf seçimi erken karamsarlıktır.
Robert Harvey,

@RobertHarvey "Öncelikle kötü veri yapısı seçimi" ile ne demek istediğinizi anlamadım. Benzer performans gösteren çoğu değişken veri yapısının değişken versiyonları var. Bir listeye ihtiyacınız varsa ve değişmez bir liste ve değişken bir liste kullanma seçeneğiniz varsa, uygulamanızdaki darboğazdan emin oluncaya dek değişmez listeye geçin ve değişken sürüm daha iyi performans gösterir.
Doval

2
@Doval: Okasaki'yi okudum. Haskell veya Lisp gibi fonksiyonel paradigmayı tam olarak destekleyen bir dil kullanmıyorsanız, bu veri yapıları yaygın olarak kullanılmaz. Ve değişmez yapıların varsayılan tercih olduğu fikrine itiraz ediyorum; Ticari bilgi işlem sistemlerinin büyük çoğunluğu hala değişken yapılar (yani ilişkisel veritabanları) etrafında tasarlanmaktadır. Değişmez veri yapılarına başlamak güzel bir fikir ama yine de çok fildişi kule.
Robert Harvey,

6

Bir nesnenin değişmez olup olmadığına karar vermenin iki ana yolu vardır.

a) Nesnenin niteliğine göre

Bu durumları yakalamak kolaydır çünkü bu nesnelerin yapıldıktan sonra değişmeyeceğini biliyoruz. Örneğin, eğer bir RequestHistoryvarlığınız varsa ve doğaya göre varlıklar bir kez kurulduktan sonra değişmezler. Bu nesneler değişmez sınıflar olarak tasarlanan düz ileri olabilir. İstek Nesnesi'nin durumunu değiştirebildiği ve zaman içinde kime atandığı gibi saklanabilir olduğunu, ancak istek geçmişinin değişmediğini unutmayın. Örneğin, geçen hafta teslim edildikten sonra atanmış duruma geçtiğinde oluşturulan bir tarih unsuru vardı VE bu tarih hakkı asla değişemez. Yani bu klasik değişmez bir durum.

b) Tasarım seçimine dayanarak, dış faktörler

Bu java.lang.String örneğine benzer. Dizeler aslında zaman içinde değişebilir ancak tasarım gereği, önbellekleme / dize havuzu / eşzamanlılık faktörleri nedeniyle onu değişmez hale getirdiler. Benzer şekilde, önbellekleme / eşzamanlılık vb. Uygulamada önbellekleme / eşzamanlılık ve buna bağlı performans hayati önem taşıyorsa, nesnenin değişmez hale getirilmesinde iyi bir rol oynayabilir. Ancak bu karar, tüm etkileri açıkladıktan sonra çok dikkatli bir şekilde alınmalıdır.

Değiştirilemeyen cisimlerin temel avantajı, yabani ot yabani ot desenine maruz kalmamasıdır. Nesne, yaşam süresi boyunca herhangi bir değişiklik yapmaz ve kodlama ve bakımı çok daha kolay hale getirir.


4

Şu anda kendi projemdeki birkaç veri türünün değişmez hale getirilip değiştirilmeyeceğini tartışıyorum, ancak bunu akranlarıma doğrulamak zorunda kalacağım ve türlerdeki veriler (ÇOK nadiren) değişebilir - tabii ki istediğiniz zaman değiştirebilirsiniz değişmez yolu (değiştirmek istediğinizler hariç eski nesneden özellikleri kopyalayarak, yeni bir tane oluşturun).

Bir programın durumunu en aza indirgemek oldukça faydalıdır.

Onlara, bir istemci sınıfından geçici depolama için sınıflarınızdan birinde değişken bir değer türü kullanmak isteyip istemediklerini sorun.

Evet derlerse nedenini sor? Değişken devlet böyle bir senaryoda yer almaz. Onları gerçekte ait oldukları durumu oluşturmaya zorlamak ve veri türlerinizin durum bilgisini olabildiğince açık hale getirmek mükemmel sebeplerdir.


4

Cevap biraz dile bağlı. Kodunuz Java gibi görünüyor, bu sorunun mümkün olduğu kadar zor olduğu bir yer. Java'da nesneler yalnızca referans olarak iletilebilir ve klon tamamen bozulur.

Basit bir cevap yok, ancak kesin olarak küçük ish değer nesnelerini değiştirilemez hale getirmek istiyorsunuz. Java, Dizeleri değiştirilemez hale getirdi, ancak yanlış Tarih ve Takvim değiştirilebildi.

Bu yüzden kesinlikle küçük değerli nesneleri değiştirilemez yapın ve bir kopya kurucu uygulayın. Cloneable'i tamamen unutun, o kadar kötü tasarlandı ki işe yaramaz.

Daha büyük değerli nesneler için, bunları değiştirilemez hale getirmek uygun değilse, kopyalanmaları kolaylaşır.


C veya C ++ yazarken yığın ve yığın arasında seçim yapmayı çok benziyor :)
Ricket

@ Ricket: çok fazla IMO değil. Yığın / yığın, nesnenin ömrüne bağlıdır. C ++ 'da yığında değişken nesneler olması çok yaygındır.
kevin cline

1

Belki de daha sonra bu tellerin değerini değiştirmezler ve asla gerek duymazlar. Açıkçası, eğer bu şeyler doğruysa, nesne değişmez olarak tasarlanmalı, değil mi?

Biraz sezgisel olarak, daha sonra hiçbir zaman dizeleri değiştirmeye ihtiyaç duymamak, nesnelerin değişmez olup olmadığının önemli olmadığı konusunda oldukça iyi bir argümandır. Programcı zaten derleyicinin onu uygulayıp uygulamadığı konusunda onları etkin bir şekilde değişmez olarak görüyor.

Taklit edilebilirlik genellikle zarar vermez, ancak her zaman da yardımcı olmuyor. Nesnenizin değişmezlikten yararlanıp fayda sağlayamayacağını anlamanın kolay yolu, değiştirmeden önce nesnenin bir kopyasını almanız veya bir muteks edinmeniz gerekip gerekmediğidir . Asla değişmezse, değişmezlik size gerçekten bir şey almaz ve bazen işleri daha karmaşık hale getirir.

Sen yapmak geçersiz bir durumda bir nesne inşa riski hakkında iyi bir nokta var, ama bu gerçekten değişmezlik ayrı bir mesele. Bir nesne değişken her ikisi de olabilir ve her zaman inşaat sonrası geçerli bir durumda.

Bu kuralın istisnası, Java'nın ne adlandırılmış parametreleri ne de varsayılan parametreleri desteklememesi nedeniyle, bazen yalnızca aşırı yüklenmiş yapıcıları kullanarak geçerli bir nesneyi garanti eden bir sınıf tasarlamanın yanlış gelmesidir. İki özellikli davada çok fazla bir şey yok, ancak o zaman kodun diğer bölümlerinde benzer fakat daha büyük sınıflara sık sık rastlanırsa tutarlılık için söylenecek bir şey var.


Değişebilirlik tartışmalarının değişmezlik ile bir nesnenin güvenli bir şekilde maruz bırakılma veya paylaşılma biçimleri arasındaki ilişkiyi tanımadığını merak ediyorum. Derinlemesine değişmeyen bir sınıf nesnesine yapılan bir referans, güvenilmeyen kodla güvenle paylaşılabilir. Bir referansı olan herkesin asla nesneyi değiştirmemesi veya bunu yapabilecek koda maruz bırakması konusunda hiçbir zaman güvenilmezse, değişken sınıfın bir örneğine yapılan bir referans paylaşılabilir. Mutasyona uğrayabilecek bir sınıf vakasına yapılan atıflar genellikle hiç paylaşılmamalıdır. Evrenin herhangi bir yerinde bir nesneye yalnızca bir referans varsa ...
supercat

... ve hiçbir şey "kimlik kodunu" sorgulamamış, kilitlemek için kullanmamış ya da "gizli Objectözelliklerine" erişmemişse , o zaman bir nesneyi doğrudan değiştirmek, yeni bir nesneye referansla referansın üzerine yazılmasından farklı olmayacaktır. aynı ancak belirtilen değişiklik için. Java'daki en büyük anlamsal zayıflıklardan biri olan IMHO, bir değişkenin yalnızca geçici olmayan bir şeye referans vermesi gerektiğini göstermesi için hiçbir kodun bulunmadığı anlamına gelmez; referanslar, sadece bir kopyaları geri döndükten sonra muhtemelen saklayamayacak yöntemlere uygulanmalıdır.
supercat

0

Bunun aşırı derecede düşük bir görüşüne sahip olabilirim ve muhtemelen her şeyi değişmez kılmak için bu kadar basit olmayan C ve C ++ kullanıyorum, ancak daha fazla yazmak için değişmez veri türlerini bir optimizasyon detayı olarak görüyorum. yan etkilerden yoksun verimli işlevler ve geri alma sistemleri ve tahribatsız düzenleme gibi özellikleri kolayca sağlayabilirsiniz.

Örneğin, bu son derece pahalı olabilir:

/// @return A new mesh whose vertices have been transformed
/// by the specified transformation matrix.
Mesh transform(Mesh mesh, Matrix4f matrix);

... eğer Meshkalıcı bir veri yapısı olarak tasarlanmamışsa ve bunun yerine tam olarak kopyalanması gereken (bazı senaryolarda gigabaytlara yayılabilen) tam olarak kopyalanması gereken bir veri türü idi. bunun bir kısmı (yalnızca senaryo konumlarını değiştirdiğimiz yukarıdaki senaryoda olduğu gibi).

Bu yüzden değişmezliğe ulaşıp, değiştirilmemiş kısımlarının sığ kopyalanmasına ve referans sayımına izin verecek şekilde veri yapısını tasarladığımda, yukarıdaki fonksiyonun hala tüm ağları derinlemesine kopyalamak zorunda kalmadan makul bir şekilde etkin olmasına izin vermek için yazdırabilirim. iş parçacığı güvenliği, istisnai güvenlik, işlemi geri alma kabiliyeti, tahribatsız uygulama vb.

Benim durumumda her şeyi değişmez kılmak çok pahalı (en azından verimlilik açısından), bu yüzden tam olarak derin kopyalamak için çok pahalı olan sınıflar için biriktiriyorum. Bu sınıflar genellikle kafesler ve görüntüler gibi ağır veri yapılarıdır ve ben genellikle değişimleri yeni bir değişmez kopya almak için bir "oluşturucu" nesnesi aracılığıyla onlara ifade etmek için değişken bir arayüz kullanırım. Ve bunu sınıfın merkezi seviyesinde değişmez garantiler almaya çalışacak kadar yapmıyorum. Sınıfı yan etkileri olmayan fonksiyonlarda kullanmama yardımcı oluyor. MeshYukarıda değişmez hale getirme arzum doğrudan ağları değişmez yapmak değil, aynı zamanda bir ağ girişi yapan ve karşılığında büyük bir bellek ve hesaplama maliyeti ödemeden yenisini çıkaran yan etkilerden arındırılmış fonksiyonların kolayca yazılmasını sağlamaktır.

Sonuç olarak, kod kodumun tamamında yalnızca 4 değişmez veri türüne sahibim ve hepsi ağır veri yapılarıdır, ancak yan etkileri olmayan işlevleri yazmama yardımcı olmak için bunları yoğun bir şekilde kullanıyorum. Bu cevap, benim gibi, her şeyi değiştirilemez hale getirmeyi kolaylaştıran bir dilde çalışıyorsanız uygulanabilir. Bu durumda, odağınızı işlevlerinizin çoğunun yan etkilerden kaçınmasını sağlamaya yönlendirebilirsiniz; bu noktada, seçili veri yapılarını değişmez hale getirmek isteyebilirsiniz, PDS türleri, pahalı tam kopyaları önlemek için bir optimizasyon detayı olarak. Bu arada böyle bir fonksiyona sahip olduğumda:

/// @return The v1 * v2.
Vector3f vec_mul(Vector3f v1, Vector3f v2);

... o zaman tam olarak kopyalayacak kadar ucuz oldukları için vektörleri değişmez hale getirme eğilimim yok. Vektörleri, değiştirilmemiş parçaları kopyalayabilen sığ yapılara dönüştürerek burada elde edilecek performans avantajı yoktur. Bu tür maliyetler, sadece vektörün tamamını kopyalamanın maliyetinden ağır basacaktır.


-1
Foo a = new Foo();
a.setProperty1("asdf");
a.setProperty2("bcde");

Foo'nun sadece iki özelliği varsa, iki argüman alan bir kurucu yazmak kolaydır. Ancak başka bir özellik eklediğinizi varsayalım. O zaman başka bir kurucu eklemelisin:

public Foo(String a, String b, SomethingElse c)

Bu noktada, hala yönetilebilir. Ama ya 10 özellik varsa? 10 argüman içeren bir kurucu istemiyorsunuz. Oluşturucu desenini örnekler oluşturmak için kullanabilirsiniz, ancak bu karmaşıklık katar. Bu zamana kadar, "neden normal insanlar gibi tüm mülkler için belirleyiciler eklemedim" diye düşüneceksiniz?


4
On argüman içeren bir yapıcı, özellik 7 ayarlanmadığında ortaya çıkan NullPointerException değerinden daha mı kötü? Oluşturucu deseni, her yöntemde başlatılmamış özellikleri denetlemek için kullanılan koddan daha mı karmaşık? Nesne oluşturulduğunda bir kez kontrol etmek daha iyidir.
kevin cline

2
On yıl önce tam olarak bu dava için söylendiği gibi, "On parametreli bir işleminiz varsa, muhtemelen bazılarını kaçırdınız."
9000

1
Neden 10 argüman olan bir kurucu istemiyorsunuz? Çizgiyi hangi noktada çiziyorsunuz? Bence 10 argüman olan bir kurucu, değişmezliğin faydaları için ödemek için küçük bir fiyat. Ayrıca, virgüllerle ayrılmış 10 şeye sahip bir satırınız var (isteğe bağlı olarak birkaç satıra ya da daha iyi görünüyorsa 10 satıra bile) veya her özelliği ayrı ayrı kurduğunuzda yine de 10 satırınız var
Ricket

1
@ Ricket: Çünkü argümanları yanlış sıraya koyma riskini arttırır . Ayarlayıcıları veya oluşturucu şablonunu kullanıyorsanız, bu pek mümkün değildir.
Mike Baranczak

4
10 argüman içeren bir kurucunuz varsa, belki de bu argümanları kendi sınıflarına (veya sınıflarına) dahil etme ve / veya sınıf tasarımınıza eleştirel bir gözle bakmanın zamanı gelmiştir.
Adam Lear
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.