Ne zaman aynı nesneye iki referans istersiniz?


20

Java'da özel olarak, ancak diğer dillerde de olması muhtemeldir: aynı nesneye iki referans olması ne zaman yararlı olur?

Örnek:

Dog a = new Dog();
Dob b = a;

Bunun yararlı olacağı bir durum var mı? Neden atemsil edilen nesneyle etkileşim kurmak istediğinizde kullanmak için tercih edilen bir çözüm olur a?


Flyweight paterninin arkasındaki özdür .

@MichaelT Ayrıntılı olabilir misiniz?
Bassinator

7
Eğer ave b hep aynı referans Dog, o zaman hiçbir nokta yoktur. Onlar ise bazen olabilir, o zaman oldukça yararlıdır.
Gabe

Yanıtlar:


45

Bir örnek, aynı nesnenin iki ayrı listede olmasını istediğiniz zamandır :

Dog myDog = new Dog();
List dogsWithRabies = new ArrayList();
List dogsThatCanPlayPiano = new ArrayList();

dogsWithRabies.add(myDog);
dogsThatCanPlayPiano.add(myDog);
// Now each List has a reference to the same dog

Başka bir kullanım, aynı nesnenin birkaç rol oynamasıdır :

Person p = new Person("Bruce Wayne");
Person batman = p;
Person ceoOfWayneIndustries = p;

4
Üzgünüm ama Bruce Wayne örneğiniz için bir tasarım kusuru, batman ve ceo pozisyonunun sizin için rol olması gerektiğine inanıyorum.
Silviu Burcea

44
Batman'ın gizli kimliğini ortaya çıkarmak için -1.
Ampt

2
@Silviu Burcea - Bruce Wayne örneğinin iyi bir örnek olmadığına kesinlikle katılıyorum. Bir yandan, global bir metin düzenlemesi 'ceoOfWayneIndustries' ve 'batman' adını 'p' olarak değiştirdiğinde (ad çakışması veya kapsam değişikliği olmadığı varsayılarak) ve programın anlambilimi değiştiyse, bunların kırılmış bir şeyidir. Başvurulan nesne, programdaki değişken adını değil, gerçek anlamı temsil eder. Farklı semantiğe sahip olmak için, ya farklı bir nesnedir ya da referanstan (saydam olması gerekir) daha fazla davranışa sahip bir şey tarafından başvurulur ve bu nedenle 2+ referansın bir örneği değildir.
gbulmer

2
Her ne kadar Bruce Wayne yazılı olarak işe yaramazsa da, belirtilen niyetin doğru olduğuna inanıyorum . Belki daha yakın bir örnek Persona batman = new Persona("Batman"); Persona bruce = new Persona("Bruce Wayne"); Persona currentPersona = batman;- birden fazla olası değerinizin (veya mevcut değerlerin bir listesinin) ve o anda etkin / seçilmiş olan değere bir referansınızın olması olabilir.
Dan Puzey

1
@gbulmer: İki nesneye referans veremeyeceğinizi söyleyebilirim; referans currentPersonabir nesneyi veya diğerini işaret eder, fakat asla ikisini birden göstermez. Özellikle, kolayca mümkün olabilir currentPersonaedilir asla ayarlı bruceiki nesnelere referans kesinlikle değil bu durumda. Benim örnekte, söylemek, hem ediyorum batmanve currentPersonaaynı örneğe referanslar vardır, ancak programın içindeki farklı semantik anlam vermektedir.
Dan Puzey

16

Bu aslında şaşırtıcı derecede derin bir soru! Modern C ++ deneyimleri (ve Rust gibi modern C ++ dillerinden alınan diller) bunu çok sık istemediğinizi gösterir! Çoğu veri için tek veya benzersiz ("sahip olma") referansı istersiniz . Bu fikir aynı zamanda doğrusal tip sistemlerin ana nedenidir .

Bununla birlikte, o zaman bile, genellikle belleğe kısa bir süre erişmek için kullanılan ancak verilerin var olduğu sürenin önemli bir kısmını almayan kısa ömürlü "ödünç alınmış" referanslar istersiniz. Genellikle bir nesneyi farklı bir işleve bağımsız değişken olarak ilettiğinizde (Parametreler de değişkenlerdir!):

void encounter(Dog a) {
  hissAt(a);
}

void hissAt(Dog b) {
  // ...
}

Bir koşula bağlı olarak iki nesneden birini kullandığınızda, hangisini seçerseniz seçin, esas olarak aynı şeyi yaptığınızda daha az görülen bir durumdur:

Dog a, b;
Dog goodBoy = whoseAGoodBoy ? a : b;
feed(goodBoy);
walk(goodBoy);
pet(goodBoy);

Daha yaygın kullanımlara geri dönüyor, ancak yerel değişkenleri geride bırakarak alanlara dönüyoruz: Örneğin, GUI çerçevelerindeki widget'ların genellikle üst widget'ları vardır, bu nedenle on düğmeyi içeren büyük çerçevenizde en az on referans bulunur (artı biraz daha fazlası) dan onun ebeveyn ve) belki olay dinleyici vb. Her türlü nesne grafiği ve bazı nesne ağaçları (üst / kardeş referansları olanlar), her biri aynı nesneye karşılık gelen birden fazla nesneye sahiptir. Ve neredeyse her veri kümesi aslında bir grafiktir ;-)


3
"Daha az rastlanan durum" oldukça yaygındır: Nesneleri bir listede veya haritada saklar, istediğinizi alır ve gerekli işlemleri gerçekleştirirsiniz. Referanslarını haritadan veya listeden silmezsiniz.
SJuan76

1
@ SJuan76 "Daha az yaygın olan durum" sadece yerel değişkenlerden almakla ilgilidir, veri yapılarını içeren her şey son noktaya düşer.

Bu, referans sayma bellek yönetimi nedeniyle sadece bir referans için ideal midir? Eğer öyleyse, motivasyonun farklı çöp toplayıcıları olan diğer dillerle ilgili olmadığını belirtmek gerekir (örn. C #, Java, Python)
MarkJ

@MarkJ Linear tipleri sadece ideal, mevcut kodların farkında olmadan buna uymaktadır, çünkü birkaç durum için anlamsal olarak doğru olan şeydir. Potansiyel performans avantajları vardır (ancak ne referans sayımı ne de GC'leri takip etmek için, sadece hem yeniden saymayı hem de izlemeyi atlamak için bu bilgiyi gerçekten kullanan farklı bir strateji kullandığınızda yardımcı olur). Daha ilginç olanı, basit, deterministik kaynak yönetimine uygulanmasıdır. RAII ve C ++ 11 hareket semantiğini düşünün, ancak daha iyi (daha sık uygulanır ve hatalar derleyici tarafından yakalanır).

6

Geçici değişkenler: aşağıdaki sözde kodu göz önünde bulundurun.

Object getMaximum(Collection objects) {
  Object max = null;
  for (Object candidate IN objects) {
    if ((max is null) OR (candidate > max)) {
      max = candidate;
    }
  }
  return max;
}

Değişkenler maxve candidateaynı nesneyi gösterebilir, ancak değişken ataması farklı kurallar kullanılarak ve farklı zamanlarda değişir.


3

Diğer cevapları desteklemek için, aynı yerden başlayarak bir veri yapısını farklı şekilde gezmek isteyebilirsiniz. Örneğin, a'nız varsa , aşağıdaki gibi bir şey kullanarak BinaryTree a = new BinaryTree(...); BinaryTree b = aağacın en solundaki ave en sağındaki yoldan geçebilirsiniz b:

while (!a.equals(null) && !b.equals(null)) {
    a = a.left();
    b = b.right();
}

Java'yı yazdığımdan beri bir süre geçti, bu yüzden kod doğru veya mantıklı olmayabilir. Daha çok sözde kod olarak al.


3

Bu yöntem, bağlamsız olarak kullanılabilen başka bir nesneye geri çağrılan birkaç nesneniz olduğunda harikadır.

Örneğin, sekmeli bir arabiriminiz varsa Tab1, Tab2 ve Tab3 olabilir. Ayrıca, kodunuzu basitleştirmek ve kullanıcı üzerinde hangi sekmenin üzerinde olduğunu anında anlamak zorunda kalmadan azaltmak için kullanıcının hangi sekmede olduğuna bakılmaksızın ortak bir değişken kullanmak isteyebilirsiniz.

Tab Tab1 = new Tab();
Tab Tab2 = new Tab();
Tab Tab3 = new Tab();
Tab CurrentTab = new Tab();

Ardından, onClick üzerindeki numaralandırılmış sekmelerin her birinde, Geçerli Sekmeyi bu Sekmeye başvuruda bulunacak şekilde değiştirebilirsiniz.

CurrentTab = Tab3;

Şimdi kodunuzda gerçekte hangi Sekmede olduğunuzu bilmenize gerek kalmadan cezasız bir şekilde "CurrentTab" ı arayabilirsiniz. CurrentTab'ın özelliklerini de güncelleyebilirsiniz; bunlar otomatik olarak başvurulan Sekmeye akar.


3

Yararlı olması için bilinmeyen bir " " referansı b olması gereken çok sayıda senaryo vardır a. Özellikle:

  • Ne bzaman derleme zamanında neyi işaret bilmiyorum .
  • Derleme zamanında biliniyor olsun ya da olmasın, bir koleksiyon üzerinde yineleme yapmanız gereken her zaman
  • Sınırlı kapsamınız olduğunda

Örneğin:

Parametreler

public void DoSomething(Thing &t) {
}

t dış kapsamdaki bir değişkene yapılan başvurudur.

Dönüş değerleri ve diğer koşullu değerler

Thing a = Thing.Get("a");
Thing b = Thing.Get("b");
Thing biggerThing = Thing.max(a, b);
Thing z = a.IsMoreZThan(b) ? a : b;

biggerThingve zher biri ya aya b. Hangisini derleme zamanında bilmiyoruz.

Lambdas ve dönüş değerleri

Thing t = someCollection.FirstOrDefault(x => x.whatever > 123);

xbir parametredir (yukarıdaki örnek 1) ve tbir dönüş değeridir (yukarıdaki örnek 2)

Koleksiyonları

indexByName.add(t.name, t);
process(indexByName["some name"]);

index["some name"]büyük ölçüde daha sofistike bir görünümdür b. Oluşturulan ve koleksiyona doldurulmuş bir nesnenin diğer adıdır.

döngüler

foreach (Thing t in things) {
 /* `t` is a reference to a thing in a collection */
}

t bir yineleyici (önceki örnek) tarafından döndürülen bir öğeye (örnek 2) yapılan bir referanstır.


Örneklerinizi takip etmek zor. Beni yanlış anlamayın, onlardan geçtim, ama çalışmak zorundaydım. Gelecekte, kod örneklerinizi tek bir bloğa ayırmanızı öneririm (bazı notlar her birini açıklar), bir blokta birkaç ilişkili olmayan örnek koymaz.
Bassinator

1
@HCBPshenanigans Seçilen cevapları değiştirmenizi beklemiyorum; ancak, okunabilirliğe yardımcı olmak ve seçilen yanıtta eksik olan bazı kullanım durumlarını doldurmak için benimkini güncelledim.
svidgen

2

Bu çok önemli bir nokta, ama IMHO anlaşılmaya değer.

Tüm OO dilleri her zaman referansların kopyalarını oluşturur ve hiçbir zaman bir nesneyi 'görünmez' olarak kopyalamaz. OO dilleri başka bir şekilde çalıştıysa, program yazmak çok daha zor olurdu . Örneğin, işlevler ve yöntemler hiçbir zaman bir nesneyi güncelleyemez. Java ve çoğu OO dilinin, önemli bir karmaşıklık olmadan kullanımı neredeyse imkansız olacaktır.

Programdaki bir nesnenin bir anlamı olması gerekir. Örneğin, gerçek fiziksel dünyada belirli bir şeyi temsil eder. Aynı şeye birçok referans olması genellikle mantıklıdır. Örneğin, ev adresim birçok kişiye ve kuruluşa verilebilir ve bu adres her zaman aynı fiziksel yeri ifade eder. İlk nokta, nesneler genellikle spesifik, gerçek veya somut bir şeyi temsil eder; ve böylece aynı şeyle ilgili birçok referansa sahip olabilmek son derece yararlıdır. Aksi takdirde program yazmak daha zor olurdu.

Geçmek her zaman abaşka bir işlev, örneğin çağrı için bir bağımsız değişken / parametre olarak
foo(Dog aDoggy);
veya bir yöntem uygulanır a, altta yatan program kodu aynı nesneye ikinci referans üretmek üzere, referans bir kopyasını çıkarır.

Ayrıca, kopyalanmış bir referansa sahip kod farklı bir iş parçacığındaysa, her ikisi de aynı nesneye erişmek için aynı anda kullanılabilir.

Bu nedenle, çoğu yararlı programda, aynı nesneye birden fazla referans olacaktır, çünkü çoğu OO programlama dilinin semantiği budur.

Şimdi, bunu düşünürsek, referansla geçmek birçok OO dilinde mevcut tek mekanizma olduğundan (C ++ her ikisini de destekler), 'doğru' varsayılan davranış olmasını bekleyebiliriz .

IMHO, referansları kullanmak için birkaç nedenden dolayı doğru varsayılan değerdir:

  1. İki farklı yerde kullanılan bir nesnenin değerinin aynı olduğunu garanti eder. Bir nesneyi iki farklı veri yapısına (diziler, listeler vb.) Yerleştirdiğinizi ve onu değiştiren bir nesne üzerinde bazı işlemler yaptığınızı düşünün. Hata ayıklamak için bir kabus olabilir. Daha da önemlisi, bu ise , her iki veri yapıları aynı nesne veya program bir hata vardır.
  2. Kodu birkaç işleve mutlu bir şekilde yeniden düzenleyebilir veya kodu birkaç işlevden biriyle birleştirebilirsiniz ve anlambilim değişmez. Dil referans anlambilimi sağlamıyorsa, kodu değiştirmek daha da karmaşık olacaktır.

Ayrıca bir verimlilik argümanı vardır; tüm nesnelerin kopyalarını oluşturmak bir referans kopyalamaktan daha az verimlidir. Ancak, bu noktayı kaçırdığını düşünüyorum. Aynı nesneye yapılan birden fazla referans daha mantıklıdır ve kullanımı daha kolaydır, çünkü gerçek fiziksel dünyanın anlambilimiyle eşleşirler.

Yani, IMHO, genellikle aynı nesneye birden fazla referans olması mantıklıdır. Bunun bir algoritma bağlamında mantıklı olmadığı olağandışı durumlarda, çoğu dil bir 'klon' veya derin kopya yapma yeteneği sağlar. Ancak bu varsayılan değildir.

Bunun varsayılan olmaması gerektiğini iddia eden insanlar , otomatik çöp toplama sağlamayan bir dil kullanıyorlar. Örneğin, eski moda C ++. Sorun şu ki, hala gerekli olabilecek nesneleri geri istememek için 'ölü' nesneleri toplamak için bir yol bulmaları gerekiyor; aynı nesneye birden fazla referans olması bunu zorlaştırır.

Bence, C ++ yeterince düşük maliyetli çöp toplama sahip olsaydı, tüm başvurulan nesneler çöp toplanır, o zaman itirazın çoğu gider. Referans semantiğin gerekli olmadığı bazı durumlar hala olacaktır . Ancak, tecrübelerime göre, bu durumları tanımlayabilen insanlar, genellikle uygun semantiği yine de seçebilirler.

Bir C ++ programında kod büyük miktarda çöp toplama veya hafifletmek için orada olduğuna dair bazı kanıtlar olduğuna inanıyorum. Ancak, bu tür bir 'altyapı' kodunun yazılması ve sürdürülmesi maliyet ekler; dili daha kolay veya daha sağlam hale getirmek için vardır. Bu nedenle, örneğin Go dili, C ++ 'nın bazı zayıflıklarını düzeltmeye odaklanarak tasarlanmıştır ve çöp toplama dışında bir seçeneği yoktur.

Bu elbette Java bağlamında önemsizdir. Kullanımı kolay olacak şekilde tasarlandı ve çöp toplama da yapıldı. Bu nedenle, birden çok referansa sahip olmak varsayılan semantiktir ve bunlara bir referans varken nesnelerin geri kazanılmaması açısından nispeten güvenlidir. Tabii ki bir veri yapısı tarafından tutulabilirler, çünkü program bir nesne ile gerçekten bittiğinde düzgün bir şekilde toplanmaz.

Öyleyse, sorunuza geri dönerek (biraz genelleme ile), ne zaman aynı nesneye birden fazla referans istersiniz? Aklıma gelen her durumda hemen hemen. Çoğu dil parametre geçiş mekanizmasının varsayılan semantikleridir. Bunun nedeni, gerçek dünyada var olan nesnelerin işlenmesinin varsayılan semantiğinin referans olarak olması gerektiğidir (çünkü gerçek nesneler oradadır).

Diğer herhangi bir anlambilimin ele alınması daha zor olurdu.

Dog a = new Dog("rover");  // initialise with name 
DogList dl = new DogList()
dl.add(a)
...
a.setOwner("Mr Been")

Ben "rover" dltarafından etkilenen setOwnerveya programlar yazmak, anlamak, hata ayıklama veya değiştirmek zor olsun gerektiğini öneririz . Bence çoğu programcı şaşkın veya dehşete düşer.

daha sonra köpek satılır:

soldDog = dl.lookupOwner("rover", "Mr Been")
soldDog.setOwner("Mr Mcgoo")

Bu tür bir işlem yaygın ve normaldir. Bu nedenle referans anlambilimi varsayılan değerdir, çünkü genellikle en mantıklıdır.

Özet: Aynı nesneye birden fazla referans olması her zaman mantıklıdır.


iyi bir nokta, ama bu bir yorum olarak daha uygundur
Bassinator

@HCBPshenanigans - Bu nokta çok alçakgönüllü olabilir ve çok fazla söylenmemiş bırakılmış olabilir. Bu yüzden akıl yürütmeyi genişlettim. Bence bu sorunuzun 'meta' cevabı. Özet olarak, bir programdaki birçok nesne gerçek dünyadaki şeylerin belirli veya benzersiz örneklerini temsil ettiğinden, programların yazılmasını kolaylaştırmak için aynı nesneye yapılan birden çok başvuru çok önemlidir.
gbulmer

1

Tabii ki, bir başka senaryo daha ortaya çıkabilir:

Dog a = new Dog();
Dog b = a;

kodun bakımını yaptığınızda ve bfarklı bir köpek ya da farklı bir sınıf olarak kullandığınız zaman, ancak şimdi hizmet veriyor a.

Genel olarak, orta vadede, adoğrudan başvurmak için tüm kodu yeniden çalışmalısınız , ancak bu hemen gerçekleşmeyebilir.


1

Programınızın bir varlığı birden fazla noktada belleğe alma şansı olduğunda, muhtemelen farklı bileşenler kullandığından bunu yapmak istersiniz.

Kimlik Haritaları , iki veya daha fazla ayrı temsilden kaçınabilmemiz için kesin bir yerel varlık deposu sağladı. Aynı nesneyi iki kez temsil ettiğimizde, istemcimiz, nesnenin bir referansı diğer örnek olmadan önce durum değişikliklerine devam ederse bir eşzamanlılık sorununa neden olma riskiyle karşı karşıya kalır. Buradaki fikir, müşterimizin daima varlığımıza / nesnemize kesin referans verdiğinden emin olmak istiyoruz.


0

Bunu bir Sudoku çözücü yazarken kullandım. Satırları işlerken hücrenin sayısını bildiğimde, içeren sütunun sütunları işlerken hücrenin numarasını da bilmesini istiyorum. Dolayısıyla hem sütunlar hem de satırlar, çakışan Hücre nesnelerinin dizileridir. Tam olarak kabul edilen cevabın gösterdiği gibi.


-2

Web uygulamalarında, Nesne İlişkisel Eşleştiricileri, aynı veritabanı nesnesine (en azından aynı iş parçacığı içinde) yapılan tüm başvuruların aynı şeyi göstermesi için tembel yüklemeyi kullanabilir.

Örneğin, iki tablonuz varsa:

Köpekler:

  • id | owner_id | isim
  • 1 | 1 | Bark Kent

sahipleri:

  • id | isim
  • 1 | ben mi
  • 2 | sen

Aşağıdaki çağrılar yapılırsa ORM'nizin yapabileceği birkaç yaklaşım vardır:

dog = Dog.find(1)  // fetch1
owner = Owner.find(1) // fetch2
superdog = owner.dogs.first() // fetch3
superdog.name = "Superdog"
superdog.save! // write1
owner = dog.owner // fetch4
owner.name = "Mark"
owner.save! // write2
dog.owner = Owner.find(2)
dog.save! // write3

Naif stratejide, modellere ve ilgili referanslara yapılan tüm çağrılar ayrı nesneler alır. Dog.find(), Owner.find(), owner.dogsVe dog.ownerbir veritabanında sonuç onlar belleğe kaydedilir sonra ilk kez etrafında, çarptı. Ve bu yüzden:

  • veritabanı en az 4 kez getirilir
  • dog.owner, superdog.owner ile aynı değildir (ayrıca getirilir)
  • dog.name, superdog.name ile aynı değildir
  • köpek ve süper köpek her ikisi de aynı satıra yazmaya çalışır ve birbirlerinin sonuçlarının üzerine yazar: write3 write1'deki ad değişikliğini geri alır.

Referans olmadan, daha fazla getirme, daha fazla bellek kullanma ve önceki güncellemelerin üzerine yazma olasılığını tanıtma.

ORM'nizin köpekler tablosunun 1. satırına yapılan tüm referansların aynı şeye işaret etmesi gerektiğini bildiğini varsayalım. Sonra:

  • Bellekte Owner.find (1) 'e karşılık gelen bir nesne olduğu için fetch4 ortadan kaldırılabilir. fetch3, sahibi tarafından sahip olunan başka köpekler olabileceğinden, ancak en azından bir dizin taramasına neden olur, ancak satır alımını tetiklemez.
  • köpek ve süper köpek aynı nesneyi gösterir.
  • dog.name ve superdog.name aynı nesneyi gösterir.
  • dog.owner ve superdog.owner aynı nesneyi gösterir.
  • write3, write1'deki değişikliğin üzerine yazmaz.

Başka bir deyişle, referansların kullanılması, veritabanındaki satır olan tek bir doğruluk noktasının (en azından bu iş parçacığının içinde) ilkesinin kodlanmasına yardımcı olur.

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.