Когда вы хотите две ссылки на один и тот же объект?


20

В частности, в Java, но, вероятно, и в других языках: когда было бы полезно иметь две ссылки на один и тот же объект?

Пример:

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

Есть ли ситуация, когда это было бы полезно? Почему это предпочтительное решение для использования aвсякий раз, когда вы хотите взаимодействовать с объектом, представленным a?


Это суть модели Flyweight .

@MichaelT Не могли бы вы уточнить?
Басинатор

7
Если aи b всегда ссылаются на одно Dogи то же , то в этом нет никакого смысла. Если они иногда могут, то это весьма полезно.
Гейб

Ответы:


45

Например, когда вы хотите иметь один и тот же объект в двух отдельных списках :

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

Другое использование, когда у вас есть один и тот же объект, играющий несколько ролей :

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

4
Извините, но я верю, что в вашем примере с Брюсом Уэйном есть недостаток дизайна, позиция Бэтмена и генерального директора должна быть ролью для вашей личности.
Сильвиу Бурча

44
-1 за раскрытие секретной личности Бэтмена.
День

2
@Silviu Burcea - я полностью согласен, что пример Брюса Уэйна не является хорошим примером. С одной стороны, если глобальное редактирование текста изменило имя «ceoOfWayneIndustries» и «batman» на «p» (при условии отсутствия конфликтов имени или изменения области действия), и семантика программы изменилась, то это что-то не работает. Указанный объект представляет реальное значение, а не имя переменной в программе. Чтобы иметь другую семантику, это либо другой объект, либо на него ссылается что-то с большим поведением, чем ссылка (которая должна быть прозрачной), и поэтому не является примером ссылок 2+.
gbulmer

2
Хотя приведенный выше пример Брюса Уэйна может не сработать, я считаю, что заявленное намерение является верным. Возможно, более близким примером может быть Persona batman = new Persona("Batman"); Persona bruce = new Persona("Bruce Wayne"); Persona currentPersona = batman;- где у вас есть несколько возможных значений (или список доступных значений) и ссылка на текущее активное / выбранное значение.
Дэн Пьюзи

1
@gbulmer: я бы сказал, что у вас не может быть ссылки на два объекта; ссылка currentPersonaуказывает на один объект или другой, но никогда не на оба. В частности, это может легко быть возможно , что currentPersonaникогда и не установлен bruce, в этом случае это, конечно , не является ссылкой на два объекта. Я бы сказал, что в моем примере оба batmanи currentPersonaявляются ссылками на один и тот же экземпляр, но в программе используются разные смысловые значения.
Дэн Пьюзи

16

Это на самом деле удивительно глубокий вопрос! Опыт современного C ++ (и языков, основанных на современном C ++, таких как Rust) показывает, что очень часто вы этого не хотите! Для большинства данных вам нужна единственная или уникальная («владеющая») ссылка. Эта идея также является одной из основных причин систем линейного типа .

Однако даже в этом случае вам обычно нужны кратковременные «заимствованные» ссылки, которые используются для краткого доступа к памяти, но не длятся в течение значительной части времени, в течение которого существуют данные. Чаще всего, когда вы передаете объект в качестве аргумента другой функции (параметры тоже являются переменными!):

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

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

Менее распространенный случай, когда вы используете один из двух объектов в зависимости от условия, делая по существу одно и то же независимо от того, какой вы выберете:

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

Возвращаясь к более распространенному использованию, но оставляя позади локальные переменные, мы обратимся к полям: например, виджеты в платформах GUI часто имеют родительские виджеты, поэтому ваш большой фрейм, содержащий десять кнопок, будет иметь по меньшей мере десять ссылок, указывающих на него (плюс еще некоторые от его родителя и, возможно, от слушателей событий и так далее). Любой вид графа объектов и некоторые виды деревьев объектов (с родительскими / родными ссылками) имеют несколько объектов, ссылающихся на один и тот же объект. И практически каждый набор данных на самом деле является графиком ;-)


3
«Менее распространенный случай» встречается довольно часто: у вас есть объекты, хранящиеся в списке или на карте, вы получаете нужный объект и выполняете необходимые операции. Вы не стираете их ссылки с карты или списка.
SJuan76

1
@ SJuan76 «Менее распространенный случай» касается исключительно использования локальных переменных, все, что связано со структурами данных, попадает под последнюю точку.

Является ли этот идеал только одной ссылкой из-за управления памятью подсчета ссылок? Если это так, то стоит упомянуть, что мотивация не относится к другим языкам с другими сборщиками мусора (например, C #, Java, Python)
MarkJ

@MarkJ Линейные типы - это не просто идеал, так как существующий код неосознанно ему соответствует, потому что это семантически правильная вещь для многих случаев. У него есть потенциальные преимущества в производительности (но ни для подсчета ссылок, ни для отслеживания сборщиков мусора, он помогает только в том случае, если вы используете другую стратегию, которая фактически использует эти знания, чтобы исключить как повторные счета, так и отслеживание). Более интересным является его применение к простому, детерминированному управлению ресурсами. Думайте, что RAII и C ++ 11 перемещают семантику, но лучше (применяется чаще, и ошибки улавливаются компилятором).

6

Временные переменные: рассмотрим следующий псевдокод.

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

Переменные maxи candidateмогут указывать на один и тот же объект, но назначение переменных изменяется с использованием разных правил и в разное время.


3

Чтобы дополнить другие ответы, вы также можете по-разному обходить структуру данных, начиная с одного и того же места. Например, если у вас есть BinaryTree a = new BinaryTree(...); BinaryTree b = a, вы можете пройти по крайнему левому пути дерева с помощью aи к крайнему правому пути b, используя что-то вроде:

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

Прошло много времени с тех пор, как я написал Java, поэтому этот код может быть неправильным или осмысленным. Воспринимайте это как псевдокод.


3

Этот метод удобен, когда у вас есть несколько объектов, которые все обращаются к другому объекту, который можно использовать бесконтактно.

Например, если у вас есть интерфейс с вкладками, у вас могут быть Tab1, Tab2 и Tab3. Вы также можете захотеть использовать общую переменную независимо от того, на какой вкладке находится пользователь, чтобы упростить ваш код и избавиться от необходимости на лету выяснять, на какой вкладке находится ваш пользователь.

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

Затем в каждой из пронумерованных вкладок на Click вы можете изменить CurrentTab для ссылки на эту вкладку.

CurrentTab = Tab3;

Теперь в вашем коде вы можете безнаказанно вызывать «CurrentTab» без необходимости знать, на какой вкладке вы находитесь. Вы также можете обновить свойства CurrentTab, и они автоматически перетекут на указанную вкладку.


3

Существует множество сценариев, в которых b должна быть ссылка на неизвестное " a", чтобы быть полезным. Особенно:

  • Каждый раз, когда вы не знаете, что bуказывает во время компиляции.
  • Каждый раз, когда вам нужно перебрать коллекцию, известную во время компиляции или нет
  • Каждый раз, когда вы ограничены

Например:

параметры

public void DoSomething(Thing &t) {
}

t является ссылкой на переменную из внешней области видимости.

Возвращаемые значения и другие условные значения

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

biggerThingи zкаждая ссылка на или aили b. Мы не знаем, что во время компиляции.

Лямбды и их возвращаемые значения

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

xявляется параметром (пример 1 выше) и tявляется возвращаемым значением (пример 2 выше)

Коллекции

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

index["some name"]в значительной степени более сложный вид b. Это псевдоним объекта, который был создан и добавлен в коллекцию.

Loops

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

t является ссылкой на элемент, возвращенный (пример 2) итератором (предыдущий пример).


Ваши примеры трудно следовать. Не поймите меня неправильно, я прошел через них, но мне пришлось работать над этим. В будущем я предлагаю разделить ваши примеры кода на отдельные блоки (с некоторыми примечаниями, специально объясняющими каждый), а не помещать несколько несвязанных примеров в один блок.
Басинатор

1
@HCBPshenanigans Я не ожидаю, что вы измените выбранные ответы; но я обновил мой, чтобы, надеюсь, улучшить читаемость и заполнить некоторые варианты использования, отсутствующие в выбранном ответе.
svidgen

2

Это важный момент, но ИМХО стоит понять.

Все ОО-языки всегда делают копии ссылок и никогда не копируют объект «невидимо». Было бы намного сложнее писать программы, если бы ОО-языки работали как-то иначе. Например, функции и методы никогда не смогут обновить объект. Java и большинство ОО-языков было бы практически невозможно использовать без значительной дополнительной сложности.

Предполагается, что объект в программе имеет некоторый смысл. Например, оно представляет собой нечто конкретное в реальном физическом мире. Обычно имеет смысл иметь много ссылок на одну и ту же вещь. Например, мой домашний адрес может быть предоставлен многим людям и организациям, и этот адрес всегда относится к одному и тому же физическому местоположению. Итак, первое, что объекты часто представляют что-то конкретное, реальное или конкретное; и поэтому возможность иметь много ссылок на одну и ту же вещь чрезвычайно полезна. Иначе было бы сложнее писать программы.

Каждый раз, когда вы передаете aв качестве аргумента / параметра другую функцию, например, вызываете
foo(Dog aDoggy);
или применяете метод a, базовый программный код создает копию ссылки, чтобы создать вторую ссылку на тот же объект.

Кроме того, если код со скопированной ссылкой находится в другом потоке, то оба могут использоваться одновременно для доступа к одному и тому же объекту.

Поэтому в большинстве полезных программ будет несколько ссылок на один и тот же объект, потому что это семантика большинства языков программирования ОО.

Теперь, если мы подумаем об этом, поскольку передача по ссылке является единственным механизмом, доступным во многих языках OO (C ++ поддерживает оба), мы можем ожидать, что это будет «правильное» поведение по умолчанию .

ИМХО, использование ссылок является правильным по умолчанию по нескольким причинам:

  1. Это гарантирует, что ценность объекта, используемого в двух разных местах, одинакова. Представьте, что вы помещаете объект в две разные структуры данных (массивы, списки и т. Д.) И выполняете некоторые операции над объектом, который его изменяет. Это может быть кошмаром для отладки. Что еще более важно, это один и тот же объект в обеих структурах данных, или в программе есть ошибка.
  2. Вы можете успешно преобразовать код в несколько функций или объединить код из нескольких функций в одну, и семантика не изменится. Если бы язык не предоставлял ссылочную семантику, модифицировать код было бы еще сложнее.

Есть также аргумент эффективности; создание копий целых объектов менее эффективно, чем копирование ссылки. Тем не менее, я думаю, что это не так. Многократные ссылки на один и тот же объект имеют больше смысла и их легче использовать, поскольку они соответствуют семантике реального физического мира.

Так что, ИМХО, обычно имеет смысл иметь несколько ссылок на один и тот же объект. В необычных случаях, когда это не имеет смысла в контексте алгоритма, большинство языков предоставляют возможность создавать «клон» или глубокую копию. Однако это не по умолчанию.

Я думаю, что люди, которые утверждают, что это не должно быть по умолчанию, используют язык, который не обеспечивает автоматическую сборку мусора. Например, старомодный C ++. Проблема в том, что им нужно найти способ собирать «мертвые» объекты, а не возвращать объекты, которые все еще могут потребоваться; наличие нескольких ссылок на один и тот же объект усложняет задачу.

Я думаю, что если в C ++ была достаточно дешевая сборка мусора, так что все объекты, на которые ссылаются, являются сборщиком мусора, то большая часть возражений исчезнет. Все еще будут случаи, когда семантика ссылок не является необходимой. Однако, по моему опыту, люди, которые могут идентифицировать эти ситуации, также в любом случае способны выбрать подходящую семантику.

Я полагаю, что есть некоторые свидетельства того, что большой объем кода в программе на C ++ предназначен для обработки или уменьшения сбора мусора. Однако написание и поддержание такого рода «инфраструктурного» кода увеличивает стоимость; это делается для того, чтобы сделать язык более легким в использовании или более надежным. Так, например, язык Go разработан с упором на исправление некоторых слабых мест C ++, и у него нет другого выбора, кроме сборки мусора.

Это, конечно, не имеет значения в контексте Java. Он также был разработан, чтобы быть простым в использовании, а также сборка мусора. Следовательно, наличие нескольких ссылок является семантикой по умолчанию и относительно безопасно в том смысле, что объекты не возвращаются, пока есть ссылка на них. Конечно, они могут удерживаться структурой данных, потому что программа не приводит в порядок, когда она действительно закончила работу с объектом.

Итак, возвращаясь к вашему вопросу (с небольшим обобщением), когда вы захотите более одной ссылки на один и тот же объект? Практически во всех ситуациях, о которых я могу думать. Они являются семантикой по умолчанию для большинства языков механизма передачи параметров. Я полагаю, что это потому, что семантика по умолчанию для обработки объектов, которая существует в реальном мире, в значительной степени должна быть по ссылке (потому что реальные объекты там).

С любой другой семантикой будет сложнее справиться.

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

Я полагаю, что «ровер» dlдолжен быть тем, на который влияют, setOwnerили программам трудно писать, понимать, отлаживать или изменять. Я думаю, что большинство программистов были бы озадачены или встревожены в противном случае.

позже собака продается:

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

Этот вид обработки является обычным и нормальным. Так что семантика ссылок - это значение по умолчанию, потому что это обычно имеет смысл.

Резюме: всегда имеет смысл иметь несколько ссылок на один и тот же объект.


Хороший вопрос, но это лучше подходит в качестве комментария
Bassinator

@HCBPshenanigans - я думаю, что смысл, возможно, был слишком кратким и оставил слишком много недосказанного. Итак, я расширил рассуждения. Я думаю, что это «мета» ответ на ваш вопрос. Итак, множественные ссылки на один и тот же объект имеют решающее значение для облегчения написания программ, поскольку многие объекты в программе представляют собой конкретные или уникальные экземпляры вещей в реальном мире.
gbulmer

1

Конечно, еще один сценарий, в котором вы могли бы в конечном итоге:

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

это когда вы поддерживаете код и bраньше были другой собакой или другим классом, но теперь обслуживаются a.

Как правило, в среднесрочной перспективе вы должны переработать весь код, на который ссылаетесь aнапрямую, но это может произойти не сразу.


1

Вы захотите этого в любое время, когда у вашей программы есть шанс поместить объект в память более чем в одном месте, возможно, потому, что его используют разные компоненты.

Карты удостоверений предоставляют определенное локальное хранилище сущностей, поэтому мы можем избежать двух или более отдельных представлений. Когда мы представляем один и тот же объект дважды, наш клиент рискует вызвать проблему параллелизма, если одна ссылка на объект сохраняет свои изменения состояния раньше, чем другой другой экземпляр. Идея в том, что мы хотим убедиться, что наш клиент всегда имеет точную ссылку на нашу сущность / объект.


0

Я использовал это при написании решателя судоку. Когда я знаю номер ячейки при обработке строк, я хочу, чтобы содержащий столбец также знал номер этой ячейки при обработке столбцов. Таким образом, и столбцы, и строки являются массивами объектов Cell, которые перекрываются. Точно так же, как показал принятый ответ.


-2

В веб-приложениях Object Relational Mappers могут использовать отложенную загрузку, так что все ссылки на один и тот же объект базы данных (по крайней мере, в одном потоке) указывают на одну и ту же вещь.

Например, если у вас было две таблицы:

Собаки:

  • id | owner_id | имя
  • 1 | 1 | Барк Кент

Владельцы:

  • id | имя
  • 1 | мне
  • 2 | вы

Существует несколько подходов, которые ваш ORM может использовать, если выполняются следующие вызовы:

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

В наивной стратегии все обращения к моделям и связанные ссылки извлекают отдельные объекты. Dog.find(), Owner.find(), owner.dogs, И dog.ownerрезультат в базе данных попал в первый раз, после чего они будут сохранены в память. И так:

  • база данных выбирается как минимум 4 раза
  • dog.owner отличается от superdog.owner (приобретается отдельно)
  • dog.name - это не то же самое, что superdog.name
  • dog и superdog пытаются выполнить запись в одну и ту же строку и перезаписывают результаты друг друга: write3 отменяет изменение имени в write1.

Без ссылки у вас будет больше выборок, больше памяти и вы сможете перезаписать более ранние обновления.

Предположим, что ваш ORM знает, что все ссылки на строку 1 таблицы собак должны указывать на одно и то же. Потом:

  • fetch4 можно удалить, поскольку в памяти есть объект, соответствующий Owner.find (1). fetch3 все равно приведет к сканированию индекса, так как могут быть и другие собаки, принадлежащие владельцу, но не будут вызывать извлечение строки.
  • собака и супердог указывают на один и тот же объект.
  • dog.name и superdog.name указывают на один и тот же объект.
  • dog.owner и superdog.owner указывают на один и тот же объект.
  • write3 не перезаписывает изменения в write1.

Другими словами, использование ссылок помогает кодифицировать принцип единой точки правды (по крайней мере, внутри этого потока), являющейся строкой в ​​базе данных.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.