Limitation du nombre d’instances d’une classe

Dans un groupe Java sur Facebook une personne a demandé comment s’y prendre pour limiter le nombre d’instances d’une classe donnée. On pourrait imaginer que cela serve dans des cas de figures où on a besoin d’un nombre limité de « jetons », par exemple dans un environnement client-serveur.

A titre d’exercice je me suis amusé à programmer une solution. Sans nécessairement être parfaite, elle démontre comment traduire cette contrainte.

On va nommer notre classe FiveInstancesMax. Pour compter le nombre d’occurences on va utiliser un simple int, ici instanceCount, qui sera mis en static pour être accessible à partir de toutes les instances. Pour une question de reperes on peut aussi ajouter un int qui servira d’identifiant pour chaque instance

public class FiveInstancesMax {
 
private static int instanceCount = 0;
private int number = 0;
 
// ...
 
}

Pour commencer, la logique voudrait que l’on incrémente instanceCount à chaque instantiation. Le constructeur est donc tout logiquement considéré pour ce faire. Du code a été ajouté ici pour marquer l’id d’une instance et effectuer des sorties vers la console.

public FiveInstancesMax() {
instanceCount++;
this.number = instanceCount;
System.out.println("Added:" + this.number);
System.out.println("Total:" + this.instanceCount);
}

Ensuite, on en vient à la contrainte. Le branchement conditionel ici s’enclenche lorsqu’un appel est effectué et que le compteur est supérieur à un nombre donné, ici 5. Pour une approche plus OO on déclenche une Exception avec un message suffisamment explicite. Cette Exception est répercuté dans la signature du constructeur et donc un programmeur sera amené à traiter le cas de figure où l’on excéde le nombre d’instances permises.

public FiveInstancesMax() throws Exception {
if (instanceCount + 1 > 5) {
throw new Exception("Five instances maximum !");
}
instanceCount++;
this.number = instanceCount;
System.out.println("Added:" + this.number);
System.out.println("Total:" + this.instanceCount);
}

C’est ici qu’on peut se rendre compte d’une chose. Il n’y a rien qui empêche un autre programmeur de créer une sous-classe et changer le comportement du constructeur. Donc, on pourrait déclarer la classe comme final ou changer l’accesseur du constructeur de public en private.

Pour l’article je vais choisir la deuxième solution et l’ajout d’une méthode pour récupérer une instance. Pour les connaisseurs cela pourrait rappeler l’approche utilisée pour un pattern singleton classique.

// No can touch
private FiveInstancesMax() throws Exception {
public static FiveInstancesMax getInstance() throws Exception {
return new FiveInstancesMax();
}

Pour finir, il reste à trouver une approche pour décrementer le compteur lorsqu’une instance n’est plus utilisée. Comme Java dispose d’un garbage collector il n’existe pas de méthode destroy comme dans des langages comme le C++. Toutefois, il existe bien la méthode finalize qui est appellée lorsque le GC prends prend en charge une instance qui n’est plus référée.

@Override
protected void finalize() throws Throwable {
instanceCount--;
System.out.println("Removed:" + this.number);
System.out.println("Total:" + this.instanceCount);
super.finalize(); // This should be left to allow super class to also clean up.
}

Le seul bémol à cette approche est que je ne suis pas certain de la fiabilité de cette méthode. Le GC est enclenché dans certaines conditions et il est nécessaire de faire un System.gc() pour le mettre en route de manière programmatique.

Il me vient donc l’idée en écrivant l’article d’ajouter une méthode, disons unregister(), qui laissera à la classe la responsabilité de disposer d’une instance. Une amélioration possible serait d’avoir une Collection pour contenir toutes les instances et de pouvoir « tasser » ces dernières lorsque unregister est appellé.

Vous pouvez aussi considérer les changements qui seraient nécessaire pour accomoder des accès concurrents ainsi qu’un callback si le finalize est conservé. Je vous laisse l’implémentation en guise d’exercice.

Voici du code pour tester la classe qui a été élaborée.

public static void main(String... args) {
List<FiveInstancesMax> list = new ArrayList<FiveInstancesMax>();
 
// Loading.
for (int i = 0; i < 5; i++) {
try {
list.add(FiveInstancesMax.getInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
 
// Test with invocation of GC.
do {
try {
list.remove(0);
System.gc();
} catch (Exception e) {
e.printStackTrace();
}
} while(!list.isEmpty());
 
// Trigger Exception test
for (int i = 0; i < 7; i++) {
try {
list.add(FiveInstancesMax.getInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
 
}
}

12 réflexions au sujet de « Limitation du nombre d’instances d’une classe »

  1. Ping : Recap java, semaine 33, année 2012 | Blog de la rubrique java

Les commentaires sont fermés.