2010-11-02 41 views
2

J'essaie actuellement de lire des données binaires à l'aide de BinaryReader. J'ai créé une classe d'assistance pour analyser ces données. À l'heure actuelle, il est une classe statique avec ce genre de méthodes:Utilisation d'une classe statique ou d'une classe déclarée

public static class Parser 
{ 
    public static ParseObject1 ReadObject1(BinaryReader reader){...} 
    public static ParseObject2 ReadObject2(BinaryReader reader{...} 
} 

Puis-je utiliser comme ceci:

... 
BinaryReader br = new BinaryReader(@"file.ext"); 
ParseObject1 po1 = Parser.ReadObject1(br); 
... 
ParseObject1 po2 = Parser.ReadObject2(br); 
... 

Mais je commencé à penser, je pourrais aussi initialiser juste la classe comme celui-ci

Parser p = new Parser(br); 
ParseObject1 po1 = Parser.ReadObject1(); 

Ce qui serait une meilleure implémentation.

Répondre

8

Ce qui est plus rapide n'est pas vraiment pertinent ici; vos préoccupations concernent davantage la concurrence et l'architecture. Dans le cas d'une classe d'analyse statique à laquelle vous transmettez le BinaryReader en tant qu'argument de l'appel ReadObject, vous fournissez toutes les données à la méthode, et (probablement, par votre exemple) ne pas conserver de données à propos du lecteur dans l'analyseur; cela vous permet d'instancier plusieurs objets BinaryReader et d'appeler l'analyseur sur eux séparément, sans problèmes de concurrence ou de collision. (Notez que cela s'applique UNIQUEMENT si vous n'avez pas de données statiques persistantes dans votre objet Parser.)

Par contre, si votre analyseur passe l'objet BinaryReader sur lequel il doit opérer, il persistera vraisemblablement que BinaryReader contient des données dans lui-même; il y a une complication potentielle là si vous avez entrelacé des appels à votre Parser avec différents objets BinaryReader.

Si votre analyseur n'a pas besoin de maintenir l'état entre ReadObject1 et ReadObject2, je vous recommande de le garder statique et de passer la référence de l'objet BinaryReader; le garder statique dans ce cas est un bon "descripteur" du fait qu'il n'y a pas de données persistantes entre ces invocations. D'un autre côté, s'il y a des données persistantes sur le BinaryReader dans l'analyseur, je le rends non-statique, et je passe les données dedans (comme dans votre deuxième exemple). En le rendant non-statique mais avec des données persistantes en classe, il est beaucoup moins susceptible de provoquer des problèmes de concurrence.

+3

Grande réponse, je lance rarement dans les codeurs qui pensent de cette façon. – ChaosPandion

+2

D'accord, réponse vraiment géniale en effet. Cela le rend clair pour moi. Je n'y ai jamais pensé de cette façon. Plus vite n'est en effet pas toujours la définition de la meilleure implémentation. –

+0

Content de vous aider! J'ai trouvé cette façon d'analyser le problème très utile, et j'ai évité de tels problèmes de concurrence! –

1

Il y a probablement une différence de performance négligeable entre les deux implémentations. Je m'attends à ce que la lecture du fichier binaire prenne plus de 99% du temps d'exécution.

Si vous êtes vraiment préoccupé par les performances, vous pouvez envelopper les deux implémentations dans des boucles distinctes et les chronométrer.

1

La différence de performance entre ces deux approches devrait être négligeable. Personnellement, je suggère d'utiliser une approche non statique en raison de la flexibilité qu'elle offre. Si vous trouvez utile de regrouper une grande partie de la logique d'analyse en un seul endroit, vous pouvez utiliser une approche combinée (illustrée dans mon exemple ci-dessous). En ce qui concerne les performances, Si vous créez plusieurs instances de votre classe Analyser à plusieurs reprises, vous remarquerez peut-être un faible impact sur les performances, mais vous pourrez probablement refactoriser le code pour éviter de créer plusieurs fois des instances de la classe Parser. De même, l'appel d'une méthode d'instance (en particulier une méthode virtuelle) n'est techniquement pas aussi rapide que l'appel d'une méthode statique, là encore la différence de performance devrait être très négligeable.

McWafflestix soulève un bon point sur l'état.Cependant, étant donné que votre implémentation actuelle utilise des méthodes statiques, je suppose que votre classe Parser n'a pas besoin de maintenir l'état entre les appels aux méthodes Read et vous devriez donc pouvoir réutiliser la même instance d'analyseur pour analyser plusieurs objets d'un BinaryReader flux. Ci-dessous est un exemple qui illustre l'approche que je prendrais probablement pour ce problème. Voici quelques caractéristiques de cet exemple:

  • Utilisation du polymorphisme pour extraire des détails sur l'emplacement de la logique d'analyse pour un type donné d'objet.
  • Utilisation d'un référentiel pour stocker les instances de l'analyseur afin qu'elles puissent être réutilisées. Utilisation de la réflexion pour identifier la logique d'analyse pour une classe ou une structure donnée.

Notez que j'ai gardé la logique d'analyse des méthodes statiques dans la classe ParseHelper, et les méthodes d'instance Read sur les classes MyObjectAParser et MyObjectBParser utilisent ces méthodes statiques sur la classe ParseHelper. C'est juste une décision de conception que vous pouvez prendre en fonction de ce qui vous semble le plus logique concernant l'organisation de votre logique d'analyse. Je suppose qu'il serait probablement logique de déplacer une partie de la logique d'analyse spécifique au type dans les classes Parser individuelles, mais conserver une partie de la logique d'analyse générale dans une classe ParseHelper.

// define a non-generic parser interface so that we can refer to all types of parsers 
public interface IParser 
{ 
    object Read(BinaryReader reader); 
} 

// define a generic parser interface so that we can specify a Read method specific to a particular type 
public interface IParser<T> : IParser 
{ 
    new T Read(BinaryReader reader); 
} 

public abstract class Parser<T> : IParser<T> 
{ 
    public abstract T Read(BinaryReader reader); 

    object IParser.Read(BinaryReader reader) 
    { 
     return this.Read(reader); 
    } 
} 

// define a Parser attribute so that we can easily determine the correct parser for a given type 
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)] 
public class ParserAttribute : Attribute 
{ 
    public Type ParserType { get; private set; } 

    public ParserAttribute(Type parserType) 
    { 
     if (!typeof(IParser).IsAssignableFrom(parserType)) 
      throw new ArgumentException(string.Format("The type [{0}] does not implement the IParser interface.", parserType.Name), "parserType"); 

     this.ParserType = parserType; 
    } 

    public ParserAttribute(Type parserType, Type targetType) 
    { 
     // check that the type represented by parserType implements the IParser interface 
     if (!typeof(IParser).IsAssignableFrom(parserType)) 
      throw new ArgumentException(string.Format("The type [{0}] does not implement the IParser interface.", parserType.Name), "parserType"); 

     // check that the type represented by parserType implements the IParser<T> interface, where T is the type specified by targetType 
     if (!typeof(IParser<>).MakeGenericType(targetType).IsAssignableFrom(parserType)) 
      throw new ArgumentException(string.Format("The type [{0}] does not implement the IParser<{1}> interface.", parserType.Name, targetType.Name), "parserType"); 

     this.ParserType = parserType; 
    } 
} 

// let's define a couple of example classes for parsing 

// the MyObjectA class corresponds to ParseObject1 in the original question 
[Parser(typeof(MyObjectAParser))] // the parser type for MyObjectA is MyObjectAParser 
class MyObjectA 
{ 
    // ... 
} 

// the MyObjectB class corresponds to ParseObject2 in the original question 
[Parser(typeof(MyObjectAParser))] // the parser type for MyObjectB is MyObjectBParser 
class MyObjectB 
{ 
    // ... 
} 

// a static class that contains helper functions to handle parsing logic 
static class ParseHelper 
{ 
    public static MyObjectA ReadObjectA(BinaryReader reader) 
    { 
     // <code here to parse MyObjectA from BinaryReader> 
     throw new NotImplementedException(); 
    } 

    public static MyObjectB ReadObjectB(BinaryReader reader) 
    { 
     // <code here to parse MyObjectB from BinaryReader> 
     throw new NotImplementedException(); 
    } 
} 

// a parser class that parses objects of type MyObjectA from a BinaryReader 
class MyObjectAParser : Parser<MyObjectA> 
{ 
    public override MyObjectA Read(BinaryReader reader) 
    { 
     return ParseHelper.ReadObjectA(reader); 
    } 
} 

// a parser class that parses objects of type MyObjectB from a BinaryReader 
class MyObjectBParser : Parser<MyObjectB> 
{ 
    public override MyObjectB Read(BinaryReader reader) 
    { 
     return ParseHelper.ReadObjectB(reader); 
    } 
} 

// define a ParserRepository to encapsulate the logic for finding the correct parser for a given type 
public class ParserRepository 
{ 
    private Dictionary<Type, IParser> _Parsers = new Dictionary<Type, IParser>(); 

    public IParser<T> GetParser<T>() 
    { 
     // attempt to look up the correct parser for type T from the dictionary 
     Type targetType = typeof(T); 
     IParser parser; 
     if (!this._Parsers.TryGetValue(targetType, out parser)) 
     { 
      // no parser was found, so check the target type for a Parser attribute 
      object[] attributes = targetType.GetCustomAttributes(typeof(ParserAttribute), true); 
      if (attributes != null && attributes.Length > 0) 
      { 
       ParserAttribute parserAttribute = (ParserAttribute)attributes[0]; 

       // create an instance of the identified parser 
       parser = (IParser<T>)Activator.CreateInstance(parserAttribute.ParserType); 
       // and add it to the dictionary 
       this._Parsers.Add(targetType, parser); 
      } 
      else 
      { 
       throw new InvalidOperationException(string.Format("Unable to find a parser for the type [{0}].", targetType.Name)); 
      } 
     } 
     return (IParser<T>)parser; 
    } 

    // this method can be used to set up parsers without the use of the Parser attribute 
    public void RegisterParser<T>(IParser<T> parser) 
    { 
     this._Parsers[typeof(T)] = parser; 
    } 
} 

Exemple d'utilisation:

 ParserRepository parserRepository = new ParserRepository(); 

     // ... 

     IParser<MyObjectA> parserForMyObjectA = parserRepository.GetParser<MyObjectA>(); 
     IParser<MyObjectB> parserForMyObjectB = parserRepository.GetParser<MyObjectB>(); 

     using (var fs = new FileStream(@"file.ext", FileMode.Open, FileAccess.Read, FileShare.Read)) 
     { 
      BinaryReader br = new BinaryReader(fs); 

      MyObjectA objA = parserForMyObjectA.Read(br); 
      MyObjectB objB = parserForMyObjectB.Read(br); 

      // ... 
     } 

     // Notice that this code does not explicitly reference the MyObjectAParser or MyObjectBParser classes. 
+0

Wow xD c'est une assez bonne solution flexible. Cependant, il semble que cela a un peu de frais généraux pour mon cas d'utilisation actuel. Je vais garder cela, parce que cela m'a vraiment appris quelque chose et m'a donné un autre aperçu de la façon dont ces problèmes peuvent être résolus. –