web-dev-qa-db-fra.com

Est-ce une bonne pratique d'avoir l'enregistreur comme singleton?

J'avais l'habitude de passer l'enregistreur au constructeur, comme:

public class OrderService : IOrderService {
     public OrderService(ILogger logger) {
     }
}

Mais c'est assez ennuyeux, donc je l'ai utilisé une propriété ceci depuis un certain temps:

private ILogger logger = NullLogger.Instance;
public ILogger Logger
{
    get { return logger; }
    set { logger = value; }
}

Cela devient agaçant aussi - il ne fait pas sec, je dois répéter cela dans chaque classe. Je pourrais utiliser la classe de base, mais là encore - j'utilise la classe Form, j'aurais donc besoin de FormBase, etc.

    Infrastructure.Logger.Info("blabla");

MISE À JOUR: Comme Merlyn l'a correctement remarqué, je dois mentionner que dans les premier et deuxième exemples, j'utilise DI.

75
Giedrius

Cela devient ennuyeux aussi - ce n'est pas SEC

C'est vrai. Mais vous ne pouvez pas faire grand-chose pour une préoccupation transversale qui imprègne tous vos types. Vous devez utiliser l'enregistreur partout, vous devez donc avoir la propriété sur ces types.

Voyons donc ce que nous pouvons y faire.

Singleton

Les singletons sont terribles <flame-suit-on>.

Je recommande de s'en tenir à l'injection de propriété comme vous l'avez fait avec votre deuxième exemple. C'est le meilleur affacturage que vous puissiez faire sans recourir à la magie. Il vaut mieux avoir une dépendance explicite que de la cacher via un singleton.

Mais si les singletons vous font gagner beaucoup de temps, y compris tous les refactoring que vous aurez à faire (le temps de la boule de cristal!), Je suppose que vous pourrez peut-être vivre avec eux. S'il y a jamais eu une utilisation pour un Singleton, c'est peut-être ça. Gardez à l'esprit le coût si vous jamais voulez changer d'avis sera à peu près aussi élevé que possible.

Si vous faites cela, consultez les réponses des autres en utilisant le modèle Registry (voir la description), et ceux qui enregistrent une usine singleton (réinitialisable) plutôt qu'une instance d'enregistreur singleton.

Il existe d'autres alternatives qui pourraient fonctionner aussi bien sans autant de compromis, vous devez donc les vérifier en premier.

Extraits de code Visual Studio

Vous pouvez utiliser extraits de code Visual Studio pour accélérer l'entrée de ce code répétitif. Vous pourrez taper quelque chose comme loggertabet le code apparaîtra comme par magie pour vous.

Utiliser AOP pour DRY off

Vous pouvez éliminer un peu de ce code d'injection de propriété en utilisant n cadre de programmation orientée aspect (AOP) comme PostSharp pour en générer automatiquement une partie.

Cela pourrait ressembler à ceci lorsque vous aurez terminé:

[InjectedLogger]
public ILogger Logger { get; set; }

Vous pouvez également utiliser leur exemple de code de suivi de méthode pour tracer automatiquement le code d'entrée et de sortie de la méthode, ce qui pourrait éliminer la nécessité d'ajouter toutes les propriétés de l'enregistreur ensemble. Vous pouvez appliquer l'attribut au niveau de la classe ou à l'échelle de l'espace de noms:

[Trace]
public class MyClass
{
    // ...
}

// or

#if DEBUG
[Assembly: Trace( AttributeTargetTypes = "MyNamespace.*",
    AttributeTargetTypeAttributes = MulticastAttributes.Public,
    AttributeTargetMemberAttributes = MulticastAttributes.Public )]
#endif
34

J'ai mis une instance de logger dans mon conteneur d'injection de dépendances, qui injecte ensuite le logger dans les classes qui en ont besoin.

37
Daniel Rose

Bonne question. Je crois que dans la plupart des projets, l'enregistreur est un singleton.

Quelques idées me viennent à l'esprit:

  • Utilisez ServiceLocator (ou un autre Dependency Injection conteneur si vous en utilisez déjà) qui vous permet de partager l'enregistreur entre les services/classes, de cette façon, vous pouvez instancier l'enregistreur ou même plusieurs enregistreurs différents et partager via ServiceLocator qui est évidemment un singleton, une sorte de Inversion of Control . Cette approche vous offre une grande flexibilité par rapport à un processus d'instanciation et d'initialisation de l'enregistreur.
  • Si vous avez besoin d'un enregistreur presque partout - implémentez des méthodes d'extension pour le type Object afin que chaque classe puisse appeler les méthodes de l'enregistreur comme LogInfo(), LogDebug(), LogError()
23
sll

Un singleton est une bonne idée. Une meilleure idée est d'utiliser le modèle Registry , qui donne un peu plus de contrôle sur l'instanciation. À mon avis, le modèle singleton est trop proche des variables globales. Avec un registre gérant la création ou la réutilisation d'objets, il est possible de modifier ultérieurement les règles d'instanciation.

Le registre lui-même peut être une classe statique pour donner une syntaxe simple pour accéder au journal:

Registry.Logger.Info("blabla");
14
Anders Abel

Un simple singleton n'est pas une bonne idée. Il est difficile de remplacer l'enregistreur. J'ai tendance à utiliser des filtres pour mes enregistreurs (certaines classes "bruyantes" peuvent uniquement consigner les avertissements/erreurs).

J'utilise un modèle singleton combiné avec le modèle proxy pour l'usine de journalisation:

public class LogFactory
{
    private static LogFactory _instance;

    public static void Assign(LogFactory instance)
    {
        _instance = instance;
    }

    public static LogFactory Instance
    {
        get { _instance ?? (_instance = new LogFactory()); }
    }

    public virtual ILogger GetLogger<T>()
    {
        return new SystemDebugLogger();
    }
}

Cela me permet de créer un FilteringLogFactory ou juste un SimpleFileLogFactory sans changer de code (et donc en respectant le principe Open/Closed).

Exemple d'extension

public class FilteredLogFactory : LogFactory
{
    public override ILogger GetLogger<T>()
    {
        if (typeof(ITextParser).IsAssignableFrom(typeof(T)))
            return new FilteredLogger(typeof(T));

        return new FileLogger(@"C:\Logs\MyApp.log");
    }
}

Et pour utiliser la nouvelle usine

// and to use the new log factory (somewhere early in the application):
LogFactory.Assign(new FilteredLogFactory());

Dans votre classe qui devrait se connecter:

public class MyUserService : IUserService
{
    ILogger _logger = LogFactory.Instance.GetLogger<MyUserService>();

    public void SomeMethod()
    {
        _logger.Debug("Welcome world!");
    }
}
9
jgauffin

Il existe un livre Dependency Injection dans .NET. En fonction de ce dont vous avez besoin, vous devez utiliser l'interception.

Dans ce livre, il y a un diagramme qui aide à décider d'utiliser l'injection de constructeur, l'injection de propriété, l'injection de méthode, le contexte ambiant, l'interception.

Voilà comment on raisonne en utilisant ce diagramme:

  1. Avez-vous une dépendance ou en avez-vous besoin? - Besoin de ça
  2. Est-ce une préoccupation transversale? - Oui
  3. En avez-vous besoin d'une réponse? - Non

Utiliser l'interception

3
Piotr Perak

Si vous souhaitez rechercher une bonne solution pour la journalisation, je vous suggère de regarder le moteur de l'application Google avec python où la journalisation est aussi simple que import logging Et alors vous pouvez simplement logging.debug("my message") ou logging.info("my message") ce qui le rend vraiment aussi simple qu'il le devrait.

Java n'avait pas de bonne solution pour la journalisation, c'est-à-dire que log4j devrait être évité car il vous oblige pratiquement à utiliser des singletons qui, comme indiqué ici, sont "terribles" et j'ai eu une expérience horrible en essayant de faire de la sortie de journalisation la même instruction de journalisation une seule fois lorsque je soupçonne que la raison de la double journalisation est que j'ai un Singleton de l'objet de journalisation dans deux chargeurs de classe dans la même machine virtuelle (!)

Je vous demande pardon de ne pas être aussi spécifique à C # mais d'après ce que j'ai vu, les solutions avec C # semblent similaires Java où nous avions log4j et nous devrions également en faire un singleton.

C'est pourquoi j'ai vraiment aimé la solution avec GAE/python , c'est aussi simple que cela peut être et vous n'avez pas à vous soucier des chargeurs de classe, de la double déclaration de journalisation ou de tout modèle de conception du tout d'ailleurs .

J'espère que certaines de ces informations peuvent être pertinentes pour vous et j'espère que vous voulez jeter un coup d'œil à la solution de journalisation que je recommande au lieu de celle que j'intimide sur la quantité de problème que Singleton peut suspecter en raison de l'impossibilité d'avoir un vrai singleton quand il doit être instanciant dans plusieurs chargeurs de classe.

0
Niklas Rosencrantz

Une autre solution que je trouve personnellement la plus simple consiste à utiliser une classe Logger statique. Vous pouvez l'appeler à partir de n'importe quelle méthode de classe sans avoir à changer de classe, par ex. ajouter l'injection de propriété, etc. C'est assez simple et facile à utiliser.

Logger::initialize ("filename.log", Logger::LEVEL_ERROR); // only need to be called once in your application

Logger::log ("my error message", Logger::LEVEL_ERROR); // to be used in every method where needed
0
Erik Kalkoken