Wann möchten Sie zwei Verweise auf dasselbe Objekt?


20

Speziell in Java, aber wahrscheinlich auch in anderen Sprachen: Wann wäre es sinnvoll, zwei Verweise auf dasselbe Objekt zu haben?

Beispiel:

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

Gibt es eine Situation, in der dies nützlich wäre? Warum ist dies die bevorzugte Lösung, awenn Sie mit dem durch dargestellten Objekt interagieren möchten a?


Es ist die Essenz hinter dem Flyweight-Muster .

@MichaelT Könnten Sie näher darauf eingehen?
Bassinator

7
Wenn aund b immer auf dasselbe Bezug genommen wird, hat das Dogkeinen Sinn. Wenn sie manchmal könnten, dann ist es sehr nützlich.
Gabe

Antworten:


45

Ein Beispiel ist, wenn Sie dasselbe Objekt in zwei separaten Listen haben möchten :

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

Eine andere Verwendung ist, wenn dasselbe Objekt mehrere Rollen spielt :

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

4
Es tut mir leid, aber ich glaube, dass es für Ihr Bruce Wayne-Beispiel einen Konstruktionsfehler gibt, Batman- und CEO-Position sollten Rollen für Ihre Person sein.
Silviu Burcea

44
-1 um Batmans geheime Identität preiszugeben.
Ampt

2
@ Silviu Burcea - Ich stimme voll und ganz zu, dass das Bruce Wayne-Beispiel kein gutes Beispiel ist. Wenn ein globaler Texteditor zum einen den Namen 'ceoOfWayneIndustries' und 'batman' in 'p' ändert (vorausgesetzt, es gibt keine Namenskonflikte oder Änderungen des Gültigkeitsbereichs) und die Semantik des Programms ändert sich, ist dies nicht mehr der Fall. Das Objekt, auf das verwiesen wird, stellt die tatsächliche Bedeutung dar, nicht den Variablennamen innerhalb des Programms. Unterschiedliche Semantik bedeutet, dass es sich entweder um ein anderes Objekt handelt oder dass auf ein Objekt verwiesen wird, das mehr Verhalten aufweist als eine Referenz (die transparent sein sollte). Daher handelt es sich nicht um ein Beispiel für 2+ Referenzen.
gbulmer

2
Obwohl das Bruce Wayne-Beispiel, wie es geschrieben wurde, möglicherweise nicht funktioniert, glaube ich, dass die angegebene Absicht richtig ist. Ein näheres Beispiel könnte sein Persona batman = new Persona("Batman"); Persona bruce = new Persona("Bruce Wayne"); Persona currentPersona = batman;- wo Sie mehrere mögliche Werte (oder eine Liste verfügbarer Werte) und einen Verweis auf den aktuell aktiven / ausgewählten haben.
Dan Puzey

1
@gbulmer: Ich würde davon ausgehen, dass Sie keinen Verweis auf zwei Objekte haben können. Die Referenz currentPersonaverweist auf das eine oder andere Objekt, aber niemals auf beides. Insbesondere könnte es leicht möglich sein , dass currentPersonawird nie auf bruce, in welchem Fall es ist sicherlich nicht ein Hinweis auf zwei Objekte. Ich würde sagen, dass in meinem Beispiel beides batmanund currentPersonaVerweise auf dieselbe Instanz sind, aber innerhalb des Programms eine andere semantische Bedeutung haben.
Dan Puzey

16

Das ist eigentlich eine überraschend tiefe Frage! Erfahrungen mit modernem C ++ (und Sprachen, die aus modernem C ++ stammen, wie z. B. Rust) legen nahe, dass Sie das sehr oft nicht wollen! Für die meisten Daten möchten Sie eine einzelne oder eindeutige ("besitzende") Referenz. Diese Idee ist auch der Hauptgrund für lineare Systeme .

Selbst dann möchten Sie jedoch in der Regel einige kurzlebige "geliehene" Referenzen, die für den kurzfristigen Zugriff auf den Speicher verwendet werden, die jedoch einen erheblichen Teil der Zeit, in der die Daten vorhanden sind, nicht ausreichen. Am häufigsten, wenn Sie ein Objekt als Argument an eine andere Funktion übergeben (Parameter sind auch Variablen!):

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

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

Ein weniger häufiger Fall, wenn Sie abhängig von einer Bedingung eines von zwei Objekten verwenden und im Wesentlichen dasselbe tun, unabhängig davon, für welches Sie sich entscheiden:

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

Zurück zu häufigeren Verwendungen, aber ohne lokale Variablen, wenden wir uns Feldern zu: Widgets in GUI-Frameworks haben häufig übergeordnete Widgets, sodass auf Ihren großen Frame mit zehn Schaltflächen mindestens zehn Verweise verweisen (plus einige weitere) von seinen Eltern und vielleicht von Event-Listenern und so weiter). Jede Art von Objektgraph und einige Arten von Objektbäumen (solche mit Eltern- / Geschwisterreferenzen) haben mehrere Objekte, die sich auf dasselbe Objekt beziehen. Und praktisch jeder Datensatz ist eine Grafik ;-)


3
Der "seltenere Fall" tritt häufig auf: Sie haben die Objekte in einer Liste oder einer Karte gespeichert, rufen das gewünschte Objekt ab und führen die erforderlichen Vorgänge aus. Sie löschen ihre Referenzen nicht aus der Karte oder der Liste.
SJuan76

1
@ SJuan76 Der "seltenere Fall" bezieht sich ausschließlich auf lokale Variablen, alles, was Datenstrukturen betrifft, fällt unter den letzten Punkt.

Ist dieses Ideal von nur einer Referenz aufgrund der Speicherverwaltung mit Referenzzählung? In diesem Fall ist zu erwähnen, dass die Motivation für andere Sprachen mit unterschiedlichen Garbage Collectors (z. B. C #, Java, Python) nicht relevant ist
MarkJ

@MarkJ Lineare Typen sind nicht nur ein idealer Code. Viele existierende Codes stimmen unwissentlich damit überein, da dies für einige Fälle die semantisch korrekte Sache ist. Es bietet potenzielle Leistungsvorteile (aber weder für die Referenzzählung noch für die Rückverfolgung von GCs. Es hilft nur, wenn Sie eine andere Strategie verwenden, die dieses Wissen tatsächlich ausnutzt, um sowohl die Nachzählung als auch die Rückverfolgung wegzulassen.). Interessanter ist die Anwendung auf einfaches, deterministisches Ressourcenmanagement. Denke, RAII und C ++ 11 verschieben die Semantik, aber besser (trifft häufiger zu und Fehler werden vom Compiler abgefangen).

6

Temporäre Variablen: Beachten Sie den folgenden Pseudocode.

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

Die Variablen maxund candidatekönnen auf dasselbe Objekt verweisen, aber die Variablenzuordnung ändert sich nach unterschiedlichen Regeln und zu unterschiedlichen Zeiten.


3

Als Ergänzung zu den anderen Antworten möchten Sie möglicherweise auch eine andere Datenstruktur durchlaufen, beginnend an derselben Stelle. Wenn Sie zum Beispiel eine hatten BinaryTree a = new BinaryTree(...); BinaryTree b = a, könnten Sie den Pfad ganz links mit aund den Pfad ganz rechts mit dem Baum bmit etwas wie folgendem abfahren :

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

Es ist schon eine Weile her, dass ich Java geschrieben habe, sodass der Code möglicherweise nicht korrekt oder sinnvoll ist. Nimm es mehr als Pseudocode.


3

Diese Methode eignet sich hervorragend, wenn Sie über mehrere Objekte verfügen, die alle auf ein anderes Objekt zugreifen, das nicht kontextbezogen verwendet werden kann.

Wenn Sie beispielsweise eine Oberfläche mit Registerkarten haben, haben Sie möglicherweise Tab1, Tab2 und Tab3. Möglicherweise möchten Sie auch eine gemeinsame Variable verwenden können, unabhängig davon, auf welcher Registerkarte sich der Benutzer befindet, um Ihren Code zu vereinfachen und zu vermeiden, dass Sie im Handumdrehen herausfinden müssen, auf welcher Registerkarte sich Ihr Benutzer befindet.

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

Anschließend können Sie in jeder der nummerierten Registerkarten auf Click die Registerkarte CurrentTab so ändern, dass sie auf diese Registerkarte verweist.

CurrentTab = Tab3;

Jetzt können Sie in Ihrem Code "CurrentTab" ungestraft aufrufen, ohne wissen zu müssen, auf welchem ​​Tab Sie sich tatsächlich befinden. Sie können auch die Eigenschaften von CurrentTab aktualisieren und sie werden automatisch auf die referenzierte Registerkarte übertragen.


3

Es gibt viele Szenarien , wobei b muss ein Hinweis auf eine unbekannte sein „ a“, um nützlich zu sein. Bestimmtes:

  • Wann immer Sie nicht wissen, worauf Sie bbeim Kompilieren achten müssen.
  • Jedes Mal, wenn Sie eine Sammlung durchlaufen müssen, unabhängig davon, ob dies zum Zeitpunkt der Kompilierung bekannt ist oder nicht
  • Jederzeit haben Sie eingeschränkten Spielraum

Beispielsweise:

Parameter

public void DoSomething(Thing &t) {
}

t ist ein Verweis auf eine Variable von außerhalb des Gültigkeitsbereichs.

Rückgabewerte und andere bedingte Werte

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

biggerThingund zsind jeweils Verweise auf entweder aoder b. Wir wissen nicht, welche zum Zeitpunkt der Kompilierung.

Lambdas und ihre Rückgabewerte

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

xist ein Parameter (Beispiel 1 oben) und tist ein Rückgabewert (Beispiel 2 oben)

Sammlungen

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

index["some name"]ist zu einem großen Teil ein anspruchsvolleres Aussehen b. Es ist ein Alias ​​für ein Objekt, das erstellt und in die Sammlung eingefügt wurde.

Schleifen

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

t ist ein Verweis auf ein Element, das von einem Iterator zurückgegeben wurde (Beispiel 2) (vorheriges Beispiel).


Ihre Beispiele sind schwer zu folgen. Versteht mich nicht falsch, ich habe sie durchstanden, aber ich musste daran arbeiten. In Zukunft empfehle ich Ihnen, Ihre Codebeispiele in einzelne Blöcke zu unterteilen (mit einigen Anmerkungen, die diese erläutern), und nicht ein paar unabhängige Beispiele in einen Block zu packen.
Bassinator

1
@HCBPshenanigans Ich erwarte nicht, dass Sie ausgewählte Antworten ändern. Ich habe meine jedoch aktualisiert, um hoffentlich die Lesbarkeit zu verbessern und einige der in der ausgewählten Antwort fehlenden Anwendungsfälle auszufüllen.
Svidgen

2

Es ist ein entscheidender Punkt, aber meiner Meinung nach ist es wert, verstanden zu werden.

Alle OO-Sprachen machen immer Kopien von Referenzen und kopieren niemals ein Objekt 'unsichtbar'. Es wäre viel schwieriger, Programme zu schreiben, wenn die OO-Sprachen anders funktionieren würden. Beispielsweise könnten Funktionen und Methoden ein Objekt niemals aktualisieren. Java und die meisten OO-Sprachen wären ohne zusätzliche Komplexität kaum zu verwenden.

Ein Objekt in einem Programm soll eine Bedeutung haben. Zum Beispiel repräsentiert es etwas Bestimmtes in der realen physischen Welt. In der Regel ist es sinnvoll, viele Verweise auf dasselbe zu haben. Beispielsweise kann meine Privatadresse vielen Personen und Organisationen zugewiesen werden, und diese Adresse bezieht sich immer auf denselben physischen Standort. Der erste Punkt ist also, dass Objekte oft etwas Spezifisches, Reales oder Konkretes darstellen. und so ist es äußerst nützlich, in der Lage zu sein, viele Verweise auf dasselbe zu haben. Sonst wäre es schwieriger, Programme zu schreiben.

Jedes Mal, wenn Sie aals Argument / Parameter an eine andere Funktion übergeben, z. B.
foo(Dog aDoggy);
eine Methode aufrufen oder anwenden a, erstellt der zugrunde liegende Programmcode eine Kopie der Referenz, um eine zweite Referenz auf dasselbe Objekt zu erstellen.

Wenn sich der Code mit einer kopierten Referenz in einem anderen Thread befindet, können beide gleichzeitig verwendet werden, um auf dasselbe Objekt zuzugreifen.

In den meisten nützlichen Programmen wird es also mehrere Verweise auf dasselbe Objekt geben, da dies die Semantik der meisten OO-Programmiersprachen ist.

Nun, wenn wir darüber nachdenken, da die Referenzübergabe der einzige Mechanismus ist, der in vielen OO-Sprachen verfügbar ist (C ++ unterstützt beide), können wir davon ausgehen, dass dies das 'richtige' Standardverhalten ist .

IMHO ist die Verwendung von Referenzen aus mehreren Gründen die richtige Standardeinstellung :

  1. Es garantiert, dass der Wert eines Objekts, das an zwei verschiedenen Orten verwendet wird, derselbe ist. Stellen Sie sich vor, Sie platzieren ein Objekt in zwei verschiedene Datenstrukturen (Arrays, Listen usw.) und führen einige Operationen an einem Objekt durch, das es ändert. Das könnte ein Albtraum sein, um zu debuggen. Noch wichtiger ist, dass es sich in beiden Datenstrukturen um dasselbe Objekt handelt oder dass das Programm einen Fehler aufweist.
  2. Sie können Code problemlos in mehrere Funktionen umgestalten oder den Code aus mehreren Funktionen in einer Funktion zusammenführen, ohne dass sich die Semantik ändert. Wenn die Sprache keine Referenzsemantik bereitstellen würde, wäre das Ändern von Code noch komplexer.

Es gibt auch ein Effizienzargument; Das Kopieren ganzer Objekte ist weniger effizient als das Kopieren einer Referenz. Ich denke jedoch, dass das den Punkt verfehlt. Mehrfachverweise auf dasselbe Objekt sind sinnvoller und einfacher zu verwenden, da sie der Semantik der realen physischen Welt entsprechen.

Also, meiner Meinung nach, ist es normalerweise sinnvoll, mehrere Verweise auf dasselbe Objekt zu haben. In den ungewöhnlichen Fällen, in denen dies im Kontext eines Algorithmus keinen Sinn ergibt, bieten die meisten Sprachen die Möglichkeit, einen "Klon" oder eine tiefe Kopie zu erstellen. Dies ist jedoch nicht die Standardeinstellung.

Ich denke, Leute, die argumentieren, dass dies nicht der Standard sein sollte, verwenden eine Sprache, die keine automatische Speicherbereinigung bietet. Zum Beispiel altmodisches C ++. Das Problem dabei ist, dass sie einen Weg finden müssen, um "tote" Objekte zu sammeln und keine Objekte zurückzugewinnen, die möglicherweise noch benötigt werden. Mehrere Verweise auf dasselbe Objekt machen dies schwierig.

Ich denke, wenn C ++ eine ausreichend kostengünstige Garbage Collection hätte, so dass alle referenzierten Objekte Garbage Collected sind, dann verschwindet ein Großteil der Einwände. Es wird immer noch Fälle geben, in denen die Referenzsemantik nicht erforderlich ist. Nach meiner Erfahrung sind die Personen, die diese Situationen identifizieren können, jedoch in der Regel auch in der Lage, die entsprechende Semantik zu wählen.

Ich glaube, es gibt Hinweise darauf, dass ein großer Teil des Codes in einem C ++ - Programm dazu dient, die Garbage Collection zu handhaben oder zu mildern. Das Schreiben und Verwalten dieser Art von "Infrastruktur" -Code verursacht jedoch zusätzliche Kosten. Es ist dazu da, die Sprache benutzerfreundlicher oder robuster zu machen. So wurde beispielsweise die Sprache Go mit dem Schwerpunkt entwickelt, einige der Schwächen von C ++ zu beheben, und es bleibt keine andere Wahl als die Garbage Collection.

Dies ist im Kontext von Java natürlich irrelevant. Es wurde ebenfalls so konzipiert, dass es einfach zu bedienen ist, ebenso wie die Garbage Collection. Daher ist das Vorhandensein mehrerer Referenzen die Standard-Semantik und in dem Sinne relativ sicher, dass Objekte nicht zurückgefordert werden, solange eine Referenz auf sie vorhanden ist. Natürlich könnten sie von einer Datenstruktur festgehalten werden, da das Programm nicht richtig aufräumt, wenn es wirklich mit einem Objekt fertig ist.

Wenn Sie sich also (mit ein wenig Verallgemeinerung) wieder Ihrer Frage widmen, wann möchten Sie mehr als einen Verweis auf dasselbe Objekt? So ziemlich in jeder Situation, die mir einfällt. Sie sind die Standardsemantik der meisten Übergabemechanismen für Sprachparameter. Ich schlage vor, das liegt daran, dass die Standardsemantik für den Umgang mit Objekten, die in der realen Welt vorhanden sind, so gut wie per Referenz festgelegt werden muss (weil die tatsächlichen Objekte da draußen sind).

Jede andere Semantik wäre schwieriger zu handhaben.

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

Ich schlage vor, dass der "Rover" dlderjenige sein sollte, der von setOwnerProgrammen ausgeführt wird oder der schwer zu schreiben, zu verstehen, zu debuggen oder zu modifizieren ist. Ich denke, die meisten Programmierer wären sonst verwirrt oder bestürzt.

später wird der Hund verkauft:

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

Diese Art der Verarbeitung ist üblich und normal. Daher ist die Referenzsemantik die Standardeinstellung, da sie normalerweise am sinnvollsten ist.

Zusammenfassung: Es ist immer sinnvoll, mehrere Referenzen auf dasselbe Objekt zu haben.


guter Punkt, aber dies ist besser als Kommentar geeignet
Bassinator

@HCBPshenanigans - Ich denke, der Punkt war vielleicht zu knapp und ließ zu viel ungesagt. Also habe ich die Argumentation erweitert. Ich denke, es ist eine "Meta" -Antwort auf Ihre Frage. Zusammenfassend gesagt sind mehrere Verweise auf dasselbe Objekt entscheidend, um das Schreiben von Programmen zu vereinfachen, da viele Objekte in einem Programm bestimmte oder eindeutige Instanzen von Dingen in der realen Welt darstellen.
gbulmer

1

Natürlich ein weiteres Szenario, in dem Sie möglicherweise Folgendes erleben:

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

ist, wenn Sie Code pflegen und b früher ein anderer Hund oder eine andere Klasse waren, aber jetzt von betreut werden a.

Im Allgemeinen sollten Sie mittelfristig den gesamten Code überarbeiten, um adirekt darauf zu verweisen. Dies kann jedoch nicht sofort geschehen.


1

Sie möchten dies immer dann, wenn Ihr Programm die Möglichkeit hat, eine Entität an mehr als einer Stelle in den Speicher zu ziehen, möglicherweise weil verschiedene Komponenten sie verwenden.

Identity Maps lieferten einen endgültigen lokalen Speicher von Entitäten, sodass wir vermeiden können, zwei oder mehr separate Darstellungen zu haben. Wenn wir dasselbe Objekt zweimal darstellen, besteht für unseren Client das Risiko, dass ein Nebenläufigkeitsproblem auftritt, wenn eine Referenz des Objekts die Statusänderungen beibehält, bevor die andere Instanz dies tut. Die Idee ist, dass wir sicherstellen möchten, dass unser Kunde immer den endgültigen Verweis auf unsere Entität / Objekt behandelt.


0

Ich habe dies beim Schreiben eines Sudoku-Lösers verwendet. Wenn ich bei der Verarbeitung von Zeilen die Nummer einer Zelle kenne, soll die übergeordnete Spalte bei der Verarbeitung von Spalten auch die Nummer dieser Zelle kennen. Daher sind sowohl Spalten als auch Zeilen Arrays von überlappenden Zellenobjekten. Genau wie die akzeptierte Antwort zeigte.


-2

In Webanwendungen können Object Relational Mappers das verzögerte Laden verwenden, sodass alle Verweise auf dasselbe Datenbankobjekt (zumindest innerhalb desselben Threads) auf dasselbe verweisen.

Wenn Sie beispielsweise zwei Tabellen hatten:

Hunde:

  • id | owner_id | Name
  • 1 | 1 | Bark Kent

Besitzer:

  • id | Name
  • 1 | mich
  • 2 | Sie

Es gibt verschiedene Ansätze, die Ihr ORM ausführen kann, wenn folgende Anrufe getätigt werden:

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

In der naiven Strategie werden bei allen Aufrufen der Modelle und den zugehörigen Referenzen separate Objekte abgerufen. Dog.find(), Owner.find(), owner.dogs, Und dog.ownerErgebnis in einer Datenbank traf beim ersten Mal, nach dem sie in dem Speicher gespeichert werden. Und so:

  • Die Datenbank wird mindestens viermal abgerufen
  • dog.owner ist nicht dasselbe wie superdog.owner (wird separat geholt)
  • dog.name ist nicht dasselbe wie superdog.name
  • dog und superdog versuchen beide, in dieselbe Zeile zu schreiben und überschreiben die Ergebnisse des anderen: write3 macht die Namensänderung in write1 rückgängig.

Ohne Referenz haben Sie mehr Abrufe, benötigen mehr Speicher und können frühere Aktualisierungen überschreiben.

Angenommen, Ihr ORM weiß, dass alle Verweise auf Zeile 1 der Hundetabelle auf dasselbe verweisen sollten. Dann:

  • fetch4 kann entfernt werden, da sich ein Objekt im Speicher befindet, das Owner.find (1) entspricht. fetch3 führt immer noch zu mindestens einem Index-Scan, da möglicherweise andere Hunde im Besitz des Besitzers sind, aber kein Zeilenabruf ausgelöst wird.
  • Hund und Superdog zeigen auf dasselbe Objekt.
  • dog.name und superdog.name zeigen auf dasselbe Objekt.
  • dog.owner und superdog.owner zeigen auf dasselbe Objekt.
  • write3 überschreibt die Änderung in write1 nicht.

Mit anderen Worten, die Verwendung von Referenzen hilft dabei, das Prinzip eines einzelnen Wahrheitspunkts (zumindest innerhalb dieses Threads) als Zeile in der Datenbank zu kodieren.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.