Modi per garantire istanze uniche di una classe?


14

Sto cercando diversi modi per garantire che ogni istanza di una determinata classe sia un'istanza identificabile in modo univoco.

Ad esempio, ho una Nameclasse con il campo name. Una volta che ho un Nameoggetto nameinizializzato su John Smith, non voglio essere in grado di creare un'istanza di un Nameoggetto diverso anche con il nome John Smith, o se si verifica un'istanza, voglio piuttosto che un riferimento all'oggetto originale venga restituito piuttosto di un nuovo oggetto.

Sono consapevole del fatto che un modo per eseguire questa operazione consiste nell'avere una factory statica che contiene uno Mapdi tutti gli oggetti Name correnti e la factory verifica che un oggetto con John Smith come nome non esista già prima di restituire un riferimento a un Nameoggetto.

Un altro modo in cui mi viene in mente la testa è avere una mappa statica nella Nameclasse e quando il costruttore viene chiamato lanciare un'eccezione se il valore passato per nameè già in uso in un altro oggetto, tuttavia sono consapevole del fatto che genera eccezioni in un costruttore è generalmente una cattiva idea .

Ci sono altri modi per raggiungere questo obiettivo?


1
Vuoi un singleton

5
dovresti andare con il primo: - fabbrica statica
Rohit Jain,

16
@MarcB op non vuole singleton. potrebbe avere molti casi della stessa classe, ma questi casi devono essere identificabili.

1
@MarcB Sono a conoscenza del modello singleton ma ho pensato che sia possibile solo un'istanza di una classe? Voglio più istanze, valori diversi. Scusa se la domanda non è chiara. modifica: ho visto solo il primo commento prima della pubblicazione.
Peanut,

2
I'm aware that one way of doing this is to have a static factory that holds a Map...Quindi perché non vuoi farlo in questo modo?
FrustratedWithFormsDesigner,

Risposte:


13

In realtà hai già risposto alla tua domanda. Il tuo primo modo dovrebbe essere più efficace qui. L'uso static factoryè sempre preferibile rispetto a constructordove pensi di poterlo fare. Quindi, puoi evitare di usarlo Constructorin questo caso, altrimenti avresti throw some exceptionse un'istanza esiste già con il nome dato.

Quindi, puoi creare un metodo factory statico: - getInstanceWithName(name)che otterrà l'istanza già disponibile con quel nome e, se non esiste, creerà una nuova istanza e renderà il tuo constructorprivato, come dovrebbe essere fatto principalmente quando si ha a che fare con static factories.

Inoltre, per questo è necessario mantenere una statica Listo Mapdi tutte le istanze uniche create, nella tua Factoryclasse.

MODIFICA : -

Dovresti certamente passare attraverso - Efficace Java - Articolo n. 1: considera le fabbriche statiche rispetto ai costruttori . Non puoi ottenere una spiegazione migliore di quel libro.


1
È qualcosa a cui l'OP ha già pensato.
BGurung,

+1 per il collegamento, che sembra affrontare chiaramente alcune delle preoccupazioni del PO.
Robert Harvey,

@RobertHarvey. Sì, quel libro è la migliore fonte per la maggior parte degli argomenti come questi. E questo è il primo elemento in effetti. :)
Rohit Jain,

+1 per Effective Java, ho il libro, seduto nella mia borsa ora in realtà :) Tuttavia ero solo curioso di sapere come altri modi per ottenere l'unicità a parte la fabbrica statica / metodo statico e l'eccezione nel costruttore. Se non ce ne sono, accetterò questo.
Peanut,

@Peanut. Sicuro. Questa è la miglior risorsa per scavare per ottenere maggiori informazioni.
Rohit Jain,

5

Le citazioni di Java efficace sembrano aggiungere molta credibilità, quindi questa risposta si basa su:

  • Efficace Java Articolo 8: obbedire al contratto generale quando si sostituisce uguale
  • Efficace elemento Java 9: ​​Sostituisci sempre l'hashCode quando esegui l'override uguale
  • Efficace elemento Java 15: ridurre al minimo la mutabilità

Vorrei fare un passo indietro e chiedermi perché ti importa se ci sono più di un'istanza di questo oggetto nome.

Raramente ho bisogno di fare questo tipo di pooling di oggetti. Suppongo che l'OP lo stia facendo in modo che possano semplicemente confrontare i loro Nameoggetti con ==. Oppure usa gli Nameoggetti dentro HashMapo simili come chiave.

Se è così, questo è qualcosa che può essere risolto attraverso la corretta implementazione diequals() .

Così:

public final class Name {
  private final String name;

  public Name(String name) {
    if (name == null) {
      name = ""; //or exception if you like
    }
    this.name = name;
  }

  public String getName() {
    return name;
  }

  @Override
  public boolean equals(Object o) {
    if (!(o instanceof Name)) {
      return false;
    }
    Name other = (Name) o;
    return other.name.equals(name);
  }

  @Override
  public int hashCode() {
    return name.hashCode();
  }
}

Una volta fatto, è vero quanto segue:

Name a = new Name("weston");
Name b = new Name("weston");
assert(a.equals(b)); //same but:
assert(a!=b); //not the same instance
//test out the Name instances in a hashmap:
HashMap<Name,Object> map = new HashMap<Name,Object>();
Object detailsIn = new Object();
map.put(a,detailsIn);
Object detailsOut = map.get(b);
assert(detailsIn==detailsOut); //the map returned the same details object
//even though we put with `a` and got with `b` thanks to our correct equals implementation

Sto indovinando il tuo obiettivo, ma in questo modo puoi usare la Nameclasse nelle mappe hash ecc. E non devono essere esattamente la stessa istanza.


Esempio, per favore.
Robert Harvey,

@RobertHarvey esempio di corretta implementazione?
Weston,

Sembra che tu ne abbia fornito uno. Ma non sono chiaro come questo sia diverso dal semplice controllo della proprietà Name per l'uguaglianza in un metodo factory statico.
Robert Harvey,

1
@RobertHarvey Ho provato a spiegare di più, sto offrendo un'alternativa completa che non richiede affatto una factory statica e sto sfidando il punto di partenza dei PO che non vogliono avere più di una istanza per nome.
Weston,

Un motivo valido per richiedere oggetti unici è se si desidera che un blocco sincronizzato utilizzi l'oggetto come monitor. Due oggetti che .equals () a vicenda non funzionavano.
Leigh Caldwell,

3
  • Crea Nameun'interfaccia
  • Crea un'interfaccia NameFactorycon un metodoName getByName(String)
  • Crea un'implementazione di NameFactorycon Map<String,WeakReference<Name>>al suo interno
  • synchronizesulla mappa per nome all'interno del getByNamemetodo prima di creare nuove istanze diName
  • Facoltativamente, utilizzare un'implementazione privata statica Namedell'interfaccia all'interno dell'implementazione diNameFactory

Questo approccio ti consentirebbe di garantire che:

  • NameEsiste solo una singola istanza in qualsiasi momento,
  • Non ci sono perdite di memoria "Lingerer" nella tua classe, quando rimangono Namepiù a lungo del necessario,
  • Il design rimane testabile con oggetti derisi, perché si utilizzano interfacce.

Non si hanno perdite di memoria con un metodo factory, sia quando si ha throwlo stesso nome invece di restituire un oggetto, sia se si restituisce un nulloggetto.
Robert Harvey,

@RobertHarvey Intendo le perdite che non sono realmente perdite, ma oggetti legittimi (i cosiddetti "indugi"). Una semplice mappa statica impedirebbe il rilascio degli oggetti valore, mentre una mappa di riferimenti deboli li manterrebbe in memoria solo finché esistono altri riferimenti attivi.
dasblinkenlight,

Ah, capisco cosa intendi. Questo potrebbe essere uno dei rari casi in cui destructorsarebbe utile.
Robert Harvey,

1
Troppo complesso. Può essere fatto come la risposta di @weston, sovrascrivere equivale e far sì che la fabbrica mantenga una raccolta di nomi.
Tulains Córdova,

@ user1598390 Uno dovrebbe rendere le cose il più semplice possibile, ma non più semplice. La "soluzione" di Weston non risolve il problema del PO (non esiste una mappa) e una raccolta di nomi crea una perdita di memoria persistente (sì, ci sono perdite di memoria in Java).
dasblinkenlight,

1

dovresti rendere privati ​​i costruttori e creare metodi come getNameInstance(String), se esiste già un oggetto con lo stesso nome (basato su una classe statica 'hastable per esempio), restituisci quel riferimento, altrimenti crei un nuovo oggetto usando il tuo costruttore privato e lo aggiungi all'hashtable


Hai ripetuto quello che ho detto nel terzo paragrafo della domanda. Ero alla ricerca di modi alternativi.
Peanut,

Mi dispiace, penso che abbiamo risposto quasi contemporaneamente perché ho controllato le risposte prima di postare il mio. In realtà, ho provato a rispondere su StackOverflown e poi è stato migrato.
HericDenis,

1

Prova a seguire. Devi tenere traccia di ogni oggetto che crei. A questo scopo sto usando Elenco. E reso privato il costruttore di classi, in modo che il controllo preliminare possa essere applicato prima di creare un'istanza

class UniqueName
    {
        private string Name;
        public int ID;
        private static int Count=0;
        static List<UniqueName> lt=new List<UniqueName>();

        private UniqueName(string Name)
        {
            this.Name = Name;
            ID = ++Count;
        }

        public static UniqueName GetUniqueueInstance(string Name)
        {
            foreach (UniqueName un in lt)
            {
                if ( string.Compare( un.Name,Name,StringComparison.InvariantCultureIgnoreCase)==0)
                    return un;
            }

            UniqueName temp=new UniqueName(Name);
            lt.Add(temp);
            return temp;
        }
    }

Questo non è Java? Hai scritto uno pseudo-codice? Oppure, in qualsiasi altra lingua? Si prega di specificarlo esplicitamente.
Rohit Jain,

Sembra C #. Akshay, dovresti imparare altre raccolte oltre a List. Questo sarebbe adatto per Dictionary<string,UniqueName>.
Weston,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.