Quando você deseja duas referências ao mesmo objeto?


20

Em Java especificamente, mas provavelmente em outras linguagens: quando seria útil ter duas referências ao mesmo objeto?

Exemplo:

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

Existe uma situação em que isso seria útil? Por que essa seria uma solução preferida para usar asempre que você quiser interagir com o objeto representado por a?


É a essência por trás do padrão Flyweight .

@MichaelT Você poderia elaborar?
Bassinator 2/08/2014

7
Se ae b sempre fizer referência ao mesmo Dog, então não faz sentido. Se às vezes podem, então é bastante útil.
Gabe

Respostas:


45

Um exemplo é quando você deseja ter o mesmo objeto em duas listas separadas :

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

Outro uso é quando você tem o mesmo objeto desempenhando várias funções :

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

4
Sinto muito, mas acredito que, para o seu exemplo de Bruce Wayne, exista uma falha de design, a posição do Batman e do CEO deve ser um papel para sua pessoa.
Silviu Burcea

44
-1 por revelar a identidade secreta do Batman.
Ampt

2
@ Silviu Burcea - Concordo plenamente que o exemplo de Bruce Wayne não é um bom exemplo. Por um lado, se uma edição global de texto alterasse o nome 'ceoOfWayneIndustries' e 'batman' para 'p' (supondo que não houvesse conflito de nome ou alteração de escopo), e a semântica do programa fosse alterada, isso seria algo quebrado. O objeto referenciado representa o significado real, não o nome da variável no programa. Para ter semântica diferente, é um objeto diferente ou é referenciado por algo com mais comportamento do que uma referência (que deve ser transparente) e, portanto, não é um exemplo de mais de 2 referências.
precisa saber é o seguinte

2
Embora o exemplo de Bruce Wayne, como está escrito, possa não funcionar, acredito que a intenção declarada esteja correta. Talvez seja um exemplo mais próximo Persona batman = new Persona("Batman"); Persona bruce = new Persona("Bruce Wayne"); Persona currentPersona = batman;- onde você tem vários valores possíveis (ou uma lista de valores disponíveis) e uma referência ao atualmente ativo / selecionado.
21411 Dan Puzey #

1
@ bulbulmer: Eu diria que você não pode ter uma referência a dois objetos; a referência currentPersonaaponta para um objeto ou outro, mas nunca para ambos. Em particular, pode ser possível que nuncacurrentPersona esteja definido como , nesse caso, certamente não é uma referência a dois objetos. Eu diria que, no meu exemplo, ambos e são referências à mesma instância, mas servem um significado semântico diferente dentro do programa. brucebatmancurrentPersona
quer

16

Essa é realmente uma pergunta surpreendentemente profunda! A experiência do C ++ moderno (e linguagens que tiram do C ++ moderno, como o Rust) sugere que muitas vezes você não quer isso! Para a maioria dos dados, você deseja uma referência única ou exclusiva ("proprietária"). Essa idéia também é a principal razão para sistemas do tipo linear .

No entanto, mesmo assim, você normalmente deseja algumas referências "emprestadas" de curta duração que são usadas para acessar brevemente a memória, mas não duram uma fração significativa do tempo em que os dados existem. Geralmente, quando você passa um objeto como argumento para uma função diferente (os parâmetros também são variáveis!):

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

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

Um caso menos comum quando você usa um dos dois objetos, dependendo de uma condição, fazendo essencialmente a mesma coisa, independentemente da sua escolha:

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

Voltando aos usos mais comuns, mas deixando as variáveis ​​locais para trás, passamos aos campos: por exemplo, widgets em estruturas de GUI geralmente têm widgets pai, portanto, seu grande quadro contendo dez botões teria pelo menos dez referências apontando para ele (além de mais algumas de seus pais e talvez de ouvintes de eventos e assim por diante). Qualquer tipo de gráfico de objetos e alguns tipos de árvores de objetos (aqueles com referências de pai / irmão) têm vários objetos, cada um com o mesmo objeto. E praticamente todo conjunto de dados é na verdade um gráfico ;-)


3
O "caso menos comum" é bastante comum: você tem os objetos armazenados em uma lista ou mapa, recupera o que deseja e executa as operações necessárias. Você não apaga suas referências do mapa ou da lista.
precisa saber é

1
@ SJuan76 O "caso menos comum" refere-se unicamente à obtenção de variáveis ​​locais, qualquer coisa que envolva estruturas de dados se enquadra no último ponto.

Esse é o ideal de apenas uma referência devido ao gerenciamento de memória com contagem de referência? Se assim for, seria vale a pena mencionar que a motivação não é relevante para outras línguas com diferentes coletores de lixo (por exemplo, C #, Java, Python)
MarkJ

Os tipos @MarkJ Linear não são apenas o ideal, pois muitos códigos existentes estão inadvertidamente em conformidade, porque é a coisa semanticamente correta para alguns casos. Ele possui vantagens em potencial de desempenho (mas nem para contagem de referência nem rastreamento de GCs, apenas ajuda quando você usa uma estratégia diferente que realmente explora esse conhecimento para omitir refcounts e rastreamento). Mais interessante é a sua aplicação ao gerenciamento simples e determinístico de recursos. Pense que o RAII e o C ++ 11 movem a semântica, mas melhor (aplica-se com mais frequência e os erros são detectados pelo compilador).

6

Variáveis ​​temporárias: considere o seguinte pseudocódigo.

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

As variáveis maxe candidatepodem apontar para o mesmo objeto, mas a atribuição de variáveis ​​muda usando regras diferentes e em momentos diferentes.


3

Para complementar as outras respostas, você também pode querer percorrer uma estrutura de dados de maneira diferente, começando no mesmo local. Por exemplo, se você tivesse umBinaryTree a = new BinaryTree(...); BinaryTree b = a , poderá percorrer o caminho mais à esquerda da árvore ae o caminho mais à direita b, usando algo como:

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

Já faz um tempo desde que escrevi Java, para que o código não seja correto ou sensato. Tome-o mais como pseudocódigo.


3

Esse método é ótimo quando você tem vários objetos que todos chamam de volta para outro objeto que pode ser usado incontextualmente.

Por exemplo, se você tiver uma interface com guias, poderá ter as Tab1, Tab2 e Tab3. Você também pode usar uma variável comum, independentemente da guia em que o usuário está, para simplificar seu código e reduzir a necessidade de descobrir rapidamente sobre a guia do usuário.

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

Em cada uma das guias numeradas do Click, você pode alterar o CurrentTab para fazer referência a essa guia.

CurrentTab = Tab3;

Agora, no seu código, você pode chamar "CurrentTab" com impunidade, sem precisar saber em qual guia está realmente. Você também pode atualizar as propriedades do CurrentTab e elas fluirão automaticamente para a guia referenciada.


3

Existem muitos cenários em que b deve ser uma referência a um " a" desconhecido para ser útil. Em particular:

  • Sempre que você não souber o que baponta no tempo de compilação.
  • A qualquer momento, você precisa iterar sobre uma coleção, conhecida ou não no momento da compilação
  • Sempre que você tiver escopo limitado

Por exemplo:

Parâmetros

public void DoSomething(Thing &t) {
}

t é uma referência a uma variável de um escopo externo.

Retornar valores e outros valores condicionais

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

biggerThinge zsão cada uma referências a aou b. Não sabemos qual em tempo de compilação.

Lambdas e seus valores de retorno

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

xé um parâmetro (exemplo 1 acima) e té um valor de retorno (exemplo 2 acima)

Colecções

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

index["some name"]é, em grande parte, uma aparência mais sofisticada b. É um alias para um objeto que foi criado e empacotado na coleção.

rotações

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

t é uma referência a um item retornado (exemplo 2) por um iterador (exemplo anterior).


Seus exemplos são difíceis de seguir. Não me interpretem mal, eu os superei, mas tive que trabalhar nisso. No futuro, sugiro separar seus exemplos de código em blocos individuais (com algumas notas explicando especificamente cada um), não colocando alguns exemplos não relacionados em um bloco.
Bassinator 2/08/2014

1
@HCBPshenanigans Não espero que você altere as respostas selecionadas; mas atualizei o meu para ajudar a facilitar a legibilidade e preencher alguns dos casos de uso ausentes da resposta selecionada.
Svidgen

2

É um ponto crucial, mas vale a pena entender o IMHO.

Todas as linguagens OO sempre fazem cópias de referências e nunca copiam um objeto 'invisivelmente'. Seria muito mais difícil escrever programas se as linguagens OO funcionassem de outra maneira. Por exemplo, funções e métodos, nunca poderiam atualizar um objeto. Java e a maioria das linguagens OO seria quase impossível de usar sem uma complexidade adicional significativa.

Um objeto em um programa deve ter algum significado. Por exemplo, representa algo específico no mundo físico real. Geralmente, faz sentido ter muitas referências à mesma coisa. Por exemplo, meu endereço residencial pode ser fornecido a muitas pessoas e organizações, e esse endereço sempre se refere ao mesmo local físico. Portanto, o primeiro ponto é que os objetos geralmente representam algo específico, real ou concreto; e, portanto, poder ter muitas referências à mesma coisa é extremamente útil. Caso contrário, seria mais difícil escrever programas.

Sempre que você passa acomo argumento / parâmetro para outra função, por exemplo, chamar
foo(Dog aDoggy);
ou aplicar um método a, o código do programa subjacente faz uma cópia da referência, para produzir uma segunda referência para o mesmo objeto.

Além disso, se o código com uma referência copiada estiver em um encadeamento diferente, ambos poderão ser usados ​​simultaneamente para acessar o mesmo objeto.

Portanto, na maioria dos programas úteis, haverá várias referências ao mesmo objeto, porque essa é a semântica da maioria das linguagens de programação OO.

Agora, se pensarmos sobre isso, porque a passagem por referência é o único mecanismo disponível em muitas linguagens OO (o C ++ suporta os dois), podemos esperar que seja o comportamento padrão "certo" .

IMHO, usar referências é o padrão certo , por algumas razões:

  1. Garante que o valor de um objeto usado em dois locais diferentes seja o mesmo. Imagine colocar um objeto em duas estruturas de dados diferentes (matrizes, listas etc.) e executar algumas operações em um objeto que o altera. Isso pode ser um pesadelo para depurar. Mais importante, é o mesmo objeto em ambas as estruturas de dados ou o programa possui um bug.
  2. Felizmente, você pode refatorar o código em várias funções ou mesclar o código de várias funções em uma, e a semântica não muda. Se o idioma não fornecesse semântica de referência, seria ainda mais complexo modificar o código.

Há também um argumento de eficiência; fazer cópias de objetos inteiros é menos eficiente do que copiar uma referência. No entanto, acho que isso é errado. Várias referências ao mesmo objeto fazem mais sentido e são mais fáceis de usar, pois correspondem à semântica do mundo físico real.

Então, IMHO, geralmente faz sentido ter várias referências ao mesmo objeto. Nos casos incomuns em que isso não faz sentido no contexto de um algoritmo, a maioria das linguagens fornece a capacidade de criar um 'clone' ou cópia detalhada. No entanto, esse não é o padrão.

Acho que as pessoas que argumentam que esse não deve ser o padrão estão usando uma linguagem que não fornece coleta automática de lixo. Por exemplo, C ++ à moda antiga. O problema é que eles precisam encontrar uma maneira de coletar objetos "mortos" e não recuperar objetos que ainda possam ser necessários; ter várias referências ao mesmo objeto dificulta isso.

Penso que, se o C ++ tivesse uma coleta de lixo suficientemente barata, para que todos os objetos referenciados sejam coletados, então grande parte da objeção desaparece. Ainda haverá alguns casos em que a semântica de referência não é o que é necessário. No entanto, na minha experiência, as pessoas que conseguem identificar essas situações também costumam ser capazes de escolher a semântica apropriada.

Acredito que há alguma evidência de que uma grande quantidade de código em um programa C ++ existe para manipular ou mitigar a coleta de lixo. No entanto, escrever e manter esse tipo de código "infra-estrutural" adiciona custo; existe para tornar o idioma mais fácil de usar ou mais robusto. Assim, por exemplo, a linguagem Go foi projetada com o objetivo de corrigir alguns dos pontos fracos do C ++ e não tem escolha, exceto a coleta de lixo.

É claro que isso é irrelevante no contexto de Java. Também foi projetado para ser fácil de usar, e também possui coleta de lixo. Portanto, ter várias referências é a semântica padrão e é relativamente segura no sentido de que os objetos não são recuperados enquanto houver uma referência a eles. É claro que eles podem ser mantidos por uma estrutura de dados porque o programa não é organizado corretamente quando realmente termina com um objeto.

Então, voltando à sua pergunta (com um pouco de generalização), quando você deseja mais de uma referência ao mesmo objeto? Praticamente em todas as situações em que consigo pensar. Eles são a semântica padrão do mecanismo de passagem de parâmetros da maioria dos idiomas. Sugiro que isso ocorre porque a semântica padrão de manipulação de objetos que existe no mundo real praticamente tem que ser por referência (porque os objetos reais estão lá fora).

Qualquer outra semântica seria mais difícil de lidar.

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

Sugiro que o "rover" dlseja o efetuado setOwnerou que os programas sejam difíceis de escrever, entender, depurar ou modificar. Eu acho que a maioria dos programadores ficaria confusa ou consternada caso contrário.

depois, o cachorro é vendido:

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

Esse tipo de processamento é comum e normal. Portanto, a semântica de referência é o padrão, porque geralmente faz mais sentido.

Resumo: sempre faz sentido ter várias referências ao mesmo objeto.


bom ponto, mas isso é mais adequado como um comentário
Bassinator

@HCBPshenanigans - Eu acho que o ponto pode ter sido muito conciso e deixado muito por dizer. Então, eu expandi o raciocínio. Eu acho que é uma resposta 'meta' para sua pergunta. Resumo, várias referências ao mesmo objeto são cruciais para facilitar a gravação de programas, porque muitos objetos em um programa representam instâncias específicas ou únicas de coisas no mundo real.
precisa saber é o seguinte

1

Obviamente, outro cenário em que você pode acabar:

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

é quando você mantém o código e bcostumava ser um cão diferente ou uma classe diferente, mas agora é atendido por a.

Geralmente, a médio prazo, você deve refazer todo o código para se referir adiretamente, mas isso pode não acontecer imediatamente.


1

Você deseja isso sempre que seu programa tiver a chance de puxar uma entidade para a memória em mais de um ponto, possivelmente porque diferentes componentes o estejam usando.

O Identity Maps forneceu um repositório local definitivo de entidades para que possamos evitar duas ou mais representações separadas. Quando representamos o mesmo objeto duas vezes, nosso cliente corre o risco de causar um problema de simultaneidade se uma referência do objeto persistir e seu estado mudar antes que a outra outra instância. A idéia é que queremos garantir que nosso cliente esteja sempre negociando a referência definitiva à nossa entidade / objeto.


0

Eu usei isso ao escrever um solucionador de Sudoku. Quando eu sei o número de uma célula ao processar linhas, quero que a coluna que contém saiba também o número dessa célula ao processar colunas. Portanto, colunas e linhas são matrizes de objetos Cell que se sobrepõem. Exatamente como a resposta aceita mostrou.


-2

Em aplicativos da Web, os Mapeadores Relacionais de Objetos podem usar carregamento lento, para que todas as referências ao mesmo objeto de banco de dados (pelo menos no mesmo encadeamento) aponte para a mesma coisa.

Por exemplo, se você tivesse duas tabelas:

Cães:

  • id | owner_id | nome
  • 1 | 1 | Bark Kent

Os Proprietários:

  • id | nome
  • 1 | mim
  • 2 vocês

Existem várias abordagens que seu ORM pode fazer se as seguintes chamadas forem feitas:

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

Na estratégia ingênua, todas as chamadas para os modelos e as referências relacionadas recuperam objetos separados. Dog.find(), Owner.find(), owner.dogs, E dog.ownerresultado em um banco de dados atingiu a primeira vez, após o que são salvos na memória. E entao:

  • o banco de dados é buscado pelo menos 4 vezes
  • dog.owner não é o mesmo que superdog.owner (buscado separadamente)
  • dog.name não é o mesmo que superdog.name
  • dog e superdog tentam escrever na mesma linha e sobrescrevem os resultados um do outro: write3 desfaz a alteração de nome em write1.

Sem uma referência, você tem mais buscas, usa mais memória e apresenta a possibilidade de substituir atualizações anteriores.

Suponha que seu ORM saiba que todas as referências à linha 1 da tabela cães devem apontar para a mesma coisa. Então:

  • fetch4 pode ser eliminado, pois existe um objeto na memória correspondente a Owner.find (1). fetch3 ainda resultará em pelo menos uma verificação de índice, pois pode haver outros cães pertencentes ao proprietário, mas não acionará uma recuperação de linha.
  • cão e super cão apontam para o mesmo objeto.
  • dog.name e superdog.name apontam para o mesmo objeto.
  • dog.owner e superdog.owner apontam para o mesmo objeto.
  • O write3 não substitui a alteração no write1.

Em outras palavras, o uso de referências ajuda a codificar o princípio de um único ponto de verdade (pelo menos nesse segmento) sendo a linha no banco de dados.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.