Dans mon travail, je suis tombé sur un tel problème de conception :
- J'ai besoin d'une instance d'un
Manager
classe par fil - Ces instances doivent être accessibles globalement, comme dans le modèle singleton, via une fonction statique.
- Chaque thread peut avoir besoin d'initialiser son instance avec des arguments différents.
- La durée de vie de ces instances devrait être contrôlable, il serait parfois avantageux de supprimer une instance et de permettre au GC de la collecter.
Les deux premiers points en feraient un "singleton par thread", si une telle chose existe.
Voici ce que j'ai trouvé (le code est simplifié, j'ai omis les contrôles de sécurité et autres) :
public class Manager {
private final static ThreadLocal<Manager> local = new ThreadLocal<Manager>();
private int x;
Manager(int argument) { x = argument; }
public static void start(int argument) { local.set(new Manager(argument); }
public static void clean() { local.remove(); }
private void doSomething1() { x++; .... }
private int doSomething2() { if (--x == 0) clean(); ... }
public static void function1() { local.get().doSomething1(); }
public static int function2() { return local.get().doSomething2(); }
}
Comme vous pouvez le constater, la fonction de nettoyage peut également être appelée depuis les méthodes privées. Remarquez également que grâce à l'utilisation de fonctions statiques, la référence à l'instance n'est jamais perdue, de sorte que les instances assignées à différents threads ne seront pas mélangées.
Cela fonctionne très bien, mais j'ai ensuite une autre exigence :
- Différents threads peuvent avoir besoin d'utiliser différentes implémentations de la classe Manager.
J'ai donc défini une interface :
public interface ManagerHandler {
void method1();
int method2();
}
Et modifié le Manager
classe :
public class Manager {
private final static ThreadLocal<ManagerHandler> local = new ThreadLocal<ManagerHandler>();
public static void start(int argument) {
ManagerHandler handler;
// depending on the context initialize handler to whatever class it is necessary
local.set(handler);
}
public static void clean() { local.remove(); }
public static void function1() { local.get().method1(); }
public static int function2() { return local.get().method2(); }
}
Un exemple de mise en œuvre ressemblerait à ceci :
public class ExampleManagerImplementation implements ManagerHandler {
private int x;
public ExampleManagerImplementation(int argument) { x = argument; }
public void method1() { x++; .... }
public int method2() { if (--x == 0) Manager.clean(); ... }
}
La classe Manager fonctionne ici comme une façade, transférant tous les appels vers le gestionnaire approprié. Il y a un gros problème avec cette approche : Je dois définir toutes les fonctions à la fois dans la classe Manager
et dans la classe ManagerHandler
l'interface. Malheureusement, Manager
ne peut pas implémenter ManagerHandler
car elle comporte des fonctions statiques plutôt que des méthodes.
La question est la suivante : pouvez-vous imaginer un moyen meilleur/plus facile d'atteindre tous les objectifs que j'ai énumérés ci-dessus sans ce problème ?
0 votes
(Je suis obligée de dire que cela réunit tant de mauvaises choses. Un "Manager" est généralement un mauvais signe. Un "Manager" sans aucune indication de ce qu'il gère dans le nom. Une référence statique à un objet effectivement mutable. Une référence statique mutable. Un champ mutable non privé. Locaux de fils (Thread locals).
0 votes
Le code est simplifié. Le vrai nom de la classe n'est pas simplement 'Manager', bien sûr il indique de quoi il s'agit ;) Je peux ajouter le modificateur d'accès privé et final si vous le souhaitez. Mais qu'y a-t-il de mal à utiliser des locals de threads et une référence statique à un objet mutable (qui est thread-safe) ?