2009-07-03 2 views
6

Voici la situation: j'ai une classe qui en fait trop. C'est principalement pour accéder aux informations de configuration, mais il y a aussi la connexion à la base de données. Il est implémenté en tant que singleton, ce qui rend les tests unitaires difficiles, car la plupart du code y est étroitement lié. Ceci est d'autant plus problématique qu'il crée une dépendance à l'import (ce que nous faisons en Python), ce qui signifie que certains modules doivent être importés dans un certain ordre. Idéalement, j'aimerais diviser cela en deux classes et en faire un non-singleton. Heureusement, mon employeur s'est félicité du fait que ce genre de test soit bon et qu'il me permette de faire des changements comme celui-ci s'il rend le code plus testable. Cependant, je doute qu'ils seront prêts à me permettre de passer trop temps là-dessus. Et je préfère régler cela progressivement plutôt que d'essayer d'être trop radical.Quel est le plus mauvais: un singleton inutile ou un objet de Dieu?

Alors, je vois trois choix ici:

  1. Pause l'objet de configuration dans un objet de configuration (singleton) et un objet de base de données (non-singleton). Cela me permettrait au moins de supprimer la base de données en tant que dépendance à l'importation.
  2. Rendez l'objet de configuration non-singleton et transmettez-le aux objets qui en ont besoin. Je pense que cela répond mieux à nos besoins à court terme, mais je pense que cela prendrait beaucoup plus de temps.
  3. Faites quelque chose que je n'avais pas pensé que vous suggérez dans votre réponse. :-)

Alors qu'est-ce que je fais?

Répondre

5

Je pense que vous êtes sur la bonne voie pour séparer en deux classes. Vous pouvez envisager d'utiliser une fabrique pour créer votre contexte/connexion de base de données si nécessaire. De cette façon, vous pouvez traiter la connexion comme une unité d'entité de travail qui est créée/éliminée selon les besoins plutôt que de conserver une seule connexion pour la durée de vie de l'objet. YMMV, cependant. En ce qui concerne la configuration, c'est un cas où je trouve qu'un singleton peut être le bon choix. Je ne voudrais pas nécessairement le jeter juste parce qu'il est difficile de faire un test unitaire. Vous pourriez envisager de le construire, cependant, pour implémenter une interface.Vous pouvez ensuite utiliser l'injection de dépendance pour fournir une instance fictive de l'interface pendant le test. Votre code de production serait construit soit pour utiliser l'instance singleton si la valeur injectée est nulle, soit pour injecter l'instance singleton. Alternativement, vous pouvez construire la classe pour permettre une ré-initialisation via une méthode privée et l'invoquer dans vos méthodes de test setup/teardown pour vous assurer qu'elle a la configuration adéquate pour vos tests. Je préfère la première à la dernière implémentation, bien que je l'utilise aussi quand je n'ai pas de contrôle direct sur l'interface. Faire les changements de façon incrémentielle est définitivement le chemin à suivre. Envelopper la fonctionnalité actuelle avec des tests, si possible, et en vous assurant que ces tests continuent après vos modifications (mais pas ceux traitant directement de vos modifications, bien sûr) est un bon moyen de s'assurer que vous ne brisez pas d'autres codes .

5

Il est difficile de savoir sans voir votre code, mais pourquoi ne pas faire ce que vous dites - le faire progressivement? D'abord faire l'étape 1, diviser la base de données.

Si c'est rapide alors revenez en arrière, et vous avez maintenant seulement 1 plus petit objet pour arrêter d'être un singleton plutôt que 2. Donc l'étape 2 devrait être plus rapide. Ou à ce stade, vous pourriez voir un autre code qui peut être refactorisé hors du singleton.

Espérons que vous pouvez étape par étape réduire ce qui est dans le singleton jusqu'à ce qu'il disparaisse, sans jamais payer une taxe de temps énorme à une étape. Par exemple, peut-être qu'une partie de la configuration pourrait être faite singleton à la fois, si les parties de la configuration sont indépendantes. Alors peut-être la configuration de l'interface graphique est-elle restée singleton alors que la configuration de fichier a été refactorisée, ou quelque chose de similaire?

+0

Je suppose que * est * quelque chose à laquelle je n'avais pas pensé. En brisant l'objet, il devient plus facile de le dé-singleton (si c'est un mot). –

+0

Que diriez-vous de desingulate? –

+1

ou peut-être pluriel? :-) –

2

L'option 1 est ce que je fais dans toutes mes applications: objet de configuration singleton et objets de base de données créés à la demande ou injectés. Avoir l'objet de cofiguration en tant que singleton a toujours été un choix parfait pour moi. Il n'y a qu'un seul fichier de configuration et je veux toujours le lire au démarrage de l'application.

+0

Mais c'est là que nous avons des problèmes. Nous voulons lire le fichier de configuration au démarrage de l'application, mais si l'objet de configuration est global, comment empêchez-vous un autre code d'essayer d'y accéder avant de l'initialiser? Je trouve que c'est beaucoup plus difficile à prévenir que ça en a l'air. –

+0

J'y arrive en faisant toute propriété qui sera accédée sur le singleton statique et en mettant toute la logique de configuration initialistaion dans son constructeur statique. Cela signifie que dès que la première tentative est faite pour accéder à une propriété sur l'objet de configuration, elle s'initialisera elle-même. J'utilise cette approche avec C# et je ne suis pas sûr si Python a la même approche avec les propriétés statiques et les constructeurs. – sipwiz

2

Excuse le fait que je ne connais pas de Python, donc j'espère que tout code pseudo logique ...

je diviser l'objet en deux parties en premier afin que les responsabilités de votre singleton sont plus petits , alors je prendrais le singleton de configuration restant et le changerais en classe normale (comme si vous alliez exécuter votre deuxième suggestion).

Une fois que je suis arrivé jusque-là je crée une nouvelle singleton d'emballage qui expose les méthodes de la classe de configuration:

class ConfigurationWrapper : IConfigurationClass 
{ 
    public static property ConfigurationWrapper Instance; 

    public property IConfigurationClass InnerClass; 

    public method GetDefaultWindowWidth() 
    { 
     return InnerClass.GetDefaultWindowWidth(); 
    } 

    etc... 
} 

Maintenant, la première chose que l'application n'est d'injecter une instance de la ConfigurationClass dans l'encapsuleur.

ConfigurationClass config = new ConfigurationClass() 
ConfigurationWrapper.Instance.InnerClass = config; 

Enfin, vous pouvez déplacer des classes qui comptent actuellement sur le singleton vers wrapper singleton (qui devrait être une recherche rapide et remplacer). Maintenant, vous pouvez convertir les classes une à la fois pour prendre l'objet config à travers leurs constructeurs pour terminer l'étape 2. Tout ce que vous n'avez pas le temps de faire peut utiliser le wrapper singleton. Une fois que vous les avez déplacés partout, vous pouvez vous débarrasser de l'emballage. D'autre part, vous pouvez ignorer le refactoring des usages et simplement injecter une classe de configuration mockée dans le wrapper singleton pour tester - essentiellement une injection de dépendance de pauvre homme.

Questions connexes