Comment garantir des instances uniques d'une classe?

14

Je recherche différentes façons de garantir que chaque instance d'une classe donnée est une instance identifiable de manière unique.

Par exemple, j'ai une Nameclasse avec le champ name. Une fois que j'ai un Nameobjet avec nameinitialisé à John Smith, je ne veux pas pouvoir instancier un autre Nameobjet également avec le nom de John Smith, ou si l'instanciation a lieu, je veux qu'une référence à l'objet d'origine soit renvoyée plutôt qu'un nouvel objet.

Je suis conscient qu'une façon de le faire est d'avoir une fabrique statique qui contient un Mapde tous les objets Name actuels et la fabrique vérifie qu'un objet avec John Smith comme nom n'existe pas déjà avant de renvoyer une référence à un Nameobjet.

Une autre façon dont je pourrais penser du haut de ma tête est d'avoir une carte statique dans la Nameclasse et lorsque le constructeur est appelé lançant une exception si la valeur transmise pour nameest déjà utilisée dans un autre objet, mais je suis conscient de lancer des exceptions chez un constructeur est généralement une mauvaise idée .

Y a-t-il d'autres moyens d'y parvenir?

Cacahuète
la source
1
Vous voulez un singleton
5
vous devriez mieux aller avec le premier: - usine statique
Rohit Jain
16
@MarcB op ne veut pas de singleton. il peut avoir plusieurs instances de la même classe, mais ces instances doivent être identifiables.
1
@MarcB Je connais le modèle singleton mais je pensais que seule une instance d'une classe était possible? Je veux plusieurs instances, différentes valeurs. Désolé si la question n'est pas claire. modifier: Je n'ai vu que le premier commentaire avant de poster.
Peanut
2
I'm aware that one way of doing this is to have a static factory that holds a Map...Alors pourquoi tu ne veux pas le faire de cette façon?
FrustratedWithFormsDesigner

Réponses:

13

En fait, vous avez déjà répondu à votre question. Votre première façon devrait être plus efficace ici. L'utilisation static factoryest toujours préférable que constructorpartout où vous pensez que vous le pouvez. Ainsi, vous pouvez éviter d'utiliser Constructordans ce cas, sinon vous auriez throw some exceptionsi une instance existe déjà avec le nom donné.

Ainsi, vous pouvez créer une méthode d'usine statique: - getInstanceWithName(name)qui obtiendra l'instance déjà disponible avec ce nom, et si elle n'existe pas, elle créera une nouvelle instance et la rendra constructorprivée, comme cela devrait être fait principalement lorsque vous traitez avec static factories.

De plus, pour cela, vous devez maintenir une statique Listou Maptoutes les instances uniques créées dans votre Factoryclasse.

MODIFIER : -

Vous devriez certainement passer par - Java efficace - Élément n ° 1: pensez aux usines statiques plutôt qu'aux constructeurs . Vous ne pouvez pas obtenir de meilleure explication que ce livre.

Rohit Jain
la source
1
C'est quelque chose auquel le PO a déjà pensé.
BGurung
+1 pour le lien, qui semble répondre clairement à certaines des préoccupations du PO.
Robert Harvey
@RobertHarvey. Oui, ce livre est la meilleure source pour la plupart des sujets comme ceux-ci. Et c'est le tout premier élément en fait. :)
Rohit Jain
+1 pour Effective Java, j'ai le livre, assis dans mon sac maintenant en fait :) Cependant, j'étais simplement curieux de voir d'autres façons de réaliser l'unicité en dehors de l'usine statique / méthode statique et de l'exception dans le constructeur. S'il n'y en a pas, je l'accepterai.
Peanut
@Cacahuète. Sûr. C'est la meilleure ressource pour creuser pour obtenir plus d'informations.
Rohit Jain
5

Les mentions de Java efficace semblent ajouter beaucoup de crédibilité, donc cette réponse s'appuie sur:

  • Élément Java efficace 8: respectez le contrat général lorsque vous remplacez égal
  • Élément Java efficace 9: toujours remplacer le hashCode lorsque vous remplacez égal
  • Élément Java efficace 15: Minimiser la mutabilité

Je prendrais un peu de recul et me demander pourquoi vous vous souciez s'il y a plus d'une instance de cet objet de nom.

J'ai rarement besoin de faire ce type de mise en commun d'objets. Je pense que l'OP fait cela afin qu'ils puissent simplement comparer leurs Nameobjets avec ==. Ou utilisez les Nameobjets à l'intérieur d'un HashMapou similaire comme clé.

Si c'est le cas, c'est quelque chose qui peut être résolu par une implémentation correcte deequals() .

Ainsi:

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();
  }
}

Une fois cela fait, ce qui suit est vrai:

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

Je devine que votre objectif, mais cette façon , vous pouvez utiliser la Nameclasse dans les cartes de hachage etc, et ils ne pas avoir à être la même instance exacte.

Weston
la source
Exemple, s'il vous plaît.
Robert Harvey
@RobertHarvey exemple d'implémentation correcte?
weston
On dirait que vous en avez fourni un. Mais je ne vois pas en quoi cela diffère de la simple vérification de l'égalité de la propriété Name dans une méthode d'usine statique.
Robert Harvey
1
@RobertHarvey J'ai essayé d'expliquer plus, je propose une alternative complète qui ne nécessite pas du tout de fabrique statique et je conteste le point de départ des OP qu'ils ne veulent pas avoir plus d'une instance par nom.
weston
Une raison valable pour exiger des objets uniques est si vous souhaitez qu'un bloc synchronisé utilise l'objet comme moniteur. Deux objets qui .equals () ne fonctionneraient pas.
Leigh Caldwell
3
  • Faire Nameune interface
  • Créer une interface NameFactoryavec une méthodeName getByName(String)
  • Créer une implémentation de NameFactoryavec un Map<String,WeakReference<Name>>dedans
  • synchronizesur la carte par nom à l'intérieur de la getByNameméthode avant de créer de nouvelles instances deName
  • Facultativement, utilisez une implémentation privée statique de l' Nameinterface dans votre implémentation de laNameFactory

Cette approche vous permettrait de vous assurer que:

  • Une seule instance Nameexiste à tout moment,
  • Il n'y a pas de fuite de mémoire "Lingerer" dans votre classe, lorsque vous restez Nameplus longtemps que nécessaire,
  • La conception reste testable avec des objets simulés, car vous utilisez des interfaces.
dasblinkenlight
la source
Vous n'avez pas de fuites de mémoire avec une méthode d'usine non plus, si vous throwlorsque le nom identique existe au lieu de renvoyer un objet, ou si vous retournez un nullobjet.
Robert Harvey
@RobertHarvey Je veux dire les fuites qui ne sont pas vraiment des fuites, mais des objets légitimes (les soi-disant "persistants"). Une simple carte statique empêcherait la libération de ses objets de valeur, tandis qu'une carte de références faibles ne les conserverait en mémoire que tant que d'autres références actives existeraient.
dasblinkenlight
Ah, je vois ce que tu veux dire. Cela pourrait être l'un des rares cas où un destructorserait pratique.
Robert Harvey
1
Trop compliqué. Cela peut être fait comme @weston answer, remplacer égal et demander à l'usine de conserver une collection de noms.
Tulains Córdova
@ user1598390 Il faut rendre la chose aussi simple que possible, mais pas plus simple. La "solution" de weston ne résout pas le problème de l'OP (il n'y a pas de carte), et une collection de noms crée une fuite de mémoire persistante (oui, il y a des fuites de mémoire en Java).
dasblinkenlight
1

vous devez rendre les constructeurs privés et créer des méthodes comme getNameInstance(String), si un objet du même nom existe déjà (basé sur une classe statique hastable par exemple), vous retournez cette référence, sinon vous créez un nouvel objet en utilisant votre constructeur privé et l'ajoutez à la table de hachage

HericDenis
la source
Vous avez répété ce que j'ai dit au 3e paragraphe de la question. J'étais après des voies alternatives.
Peanut
Je suis désolé, je pense que nous avons répondu presque en même temps parce que j'ai vérifié les réponses avant de poster la mienne. En fait, j'ai essayé d'y répondre sur StackOverflown et il a été migré.
HericDenis
1

Essayez de suivre. Vous devez garder une trace de chaque objet que vous créez. À cette fin, j'utilise List. Et a rendu le constructeur de classe privé, afin que la pré-vérification puisse être appliquée avant de créer une instance

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;
        }
    }
Akshay
la source
Ce n'est pas Java? Avez-vous écrit un pseudo-code? Ou, dans une autre langue? Veuillez le préciser explicitement.
Rohit Jain
Ressemble à C #. Akshay, vous devriez apprendre d'autres collections que List. Ce serait un bon choix pour Dictionary<string,UniqueName>.
weston