2010-06-07 10 views
6

Tengo una pequeña jerarquía de objetos que, en general, se construye a partir de datos en un Stream, pero para algunas subclases particulares, se pueden sintetizar a partir de una lista de argumentos más simple. Al encadenar a los constructores de las subclases, me encuentro con un problema para garantizar la eliminación de la secuencia sintetizada que necesita el constructor de la clase base. No se me escapó que el uso de objetos IDisposable de esta manera es posiblemente solo un grupo sucio (¿por favor asesorar?) Por razones que no he considerado, pero, aparte de este tema, parece bastante sencillo (y una buena encapsulación).Pasar objetos identificables a través de las cadenas de constructores

Códigos:

abstract class Node { 
    protected Node (Stream raw) 
    { 
     // calculate/generate some base class properties 
    } 
} 
class FilesystemNode : Node { 
    public FilesystemNode (FileStream fs) 
     : base (fs) 
    { 
     // all good here; disposing of fs not our responsibility 
    } 
} 
class CompositeNode : Node { 
    public CompositeNode (IEnumerable some_stuff) 
     : base (GenerateRaw (some_stuff)) 
    { 
     // rogue stream from GenerateRaw now loose in the wild! 
    } 

    static Stream GenerateRaw (IEnumerable some_stuff) 
    { 
     var content = new MemoryStream(); 
     // molest elements of some_stuff into proper format, write to stream 
     content.Seek (0, SeekOrigin.Begin); 
     return content; 
    } 
} 

que darse cuenta de que no disponiendo de un MemoryStream no es exactamente un caso de parada del mundo de mala ciudadanía CLR, pero todavía me pone los pelos de punta (por no mencionar que me permite no siempre usar un MemoryStream para otros subtipos). No está en el alcance, así que no puedo explícitamente Dispose() más tarde en el constructor, y agregar una declaración using en GenerateRaw() es contraproducente ya que necesito que se devuelva la ruta.

¿Hay una mejor manera de hacerlo?

ataques preventivos:

  • sí, las propiedades calculadas en el Node constructor deben ser parte de la clase base, y no deben ser calculada por (o accesible en) las subclases
  • no lo haré requiere que una transmisión pase a CompositeNode (su formato debe ser irrelevante para la persona que llama)
  • La iteración anterior tenía el cálculo del valor en la clase base como un método protegido independiente, que acabo de llamar al final de cada constructor de subtipo movió el cuerpo de GenerateRaw() a una usando instrucción en el cuerpo del constructor CompositeNode. Pero la repetición de requerir esa llamada para cada constructor y no poder garantizar que se ejecute para cada subtipo (un Node no es un Node, semánticamente, sin estas propiedades inicializadas) me dio heebie-jeebies mucho peor que el (potencial) la fuga de recursos aquí sí.
+0

sé que esto es una vieja pregunta, pero si usted recuerda. ¿Por qué no usaste matrices de bytes en parámetros de constructor en lugar de secuencias? – Oscar

Respuesta

4

CompositeNode creó la secuencia - CompositeNode tiene la responsabilidad, a menos que algún otro código indique explícitamente que lo llevará a cabo. Una opción sería que el constructor base permitiera esto a través de una sobrecarga protegida, pero el pedido significa que sería difícil saber exactamente cuándo es seguro desecharlo. Un método virtual le permitiría hacer esto, pero realmente no debería llamar a los métodos virtual en constructores.

Me pregunto si sería mejor refactorizar para que haya un método de inicialización (Load) (que llame por separado a la construcción). Tal vez un método protected virtual, y exponerlo a través de un método public static.

+0

Excelente idea con la sobrecarga del constructor base. Como no estoy usando Stream en los constructores de la subclase, creo que con seguridad, todo está bien cuando el constructor base termina de ejecutarse. Crear prototipos con una sobrecarga de argumento booleano ahora, parece una solución bastante simple que hace lo correcto. –

-1

La regla principal es que el código que crea una instancia de un objeto desechable debe eliminarlo. Si tiene un objeto IDisposable pasado a un método, debe usarlo para lo que necesita y dejarlo en blanco.

Una buena forma de asegurarse de que siempre hace esto es usar el patrón using ([IDisposable object]) { ... }, que invocará la eliminación del objeto automáticamente cuando se complete el alcance.

+0

Correcto, pero si observa el código de muestra, la secuencia se crea en el constructor CompositeNode (encadenado). Cómo deshacerme de esto es mi pregunta. ;) –

+0

¿Cuando se elimina el CompositeNode? –

+0

Parece exagerado; Explícitamente no mantengo una referencia al Stream en ningún lado, y CompositeNode no necesita ser IDisposable. –

2

Es posible que desee considerar pasar instrucciones con respecto a la disposición como/dentro de un argumento separado del constructor que acepta un IDisposable. Este es el enfoque adoptado por XmlReader.Create, que acepta un parámetro XmlReaderSettings cuya propiedad CloseInput determina si la fuente de datos subyacente se elimina cuando el XmlReader creado finalmente se elimina.

+0

Marc recibió la respuesta aceptada ya que la suya fue la primera, pero votó por el método "correcto", ya que esto es lo que terminé haciendo. ¡Gracias! –

0

Para este sencillo ejemplo, me gustaría utilizar un método InitializeFrom(Stream s):

abstract class Node 
{ 
    public Node(Stream stream) { InitializeFrom(stream); } 
    protected Node() { } 
    protected void InitializeFrom(Stream stream); 
} 

class FilesystemNode 
{ 
    public FilesystemNode(FileStream stream) : base(stream) {} 
} 

class CompositeNode 
{ 
    public CompositeNode(IEnumerable values) : base() 
    { 
     using (var stream = new MemoryStream()) 
     { 
      // init stream 
      InitializeFrom(stream); 
     } 
    } 
} 

Por lo que es virtual si usted tiene una jerarquía más profunda. Sin embargo, tiendo a encontrar un código como este un poco difícil de seguir y utilizo el patrón que he visto en los códigos completos de la biblioteca/marco: división en objetos simples (preferiblemente inmutables e ignorantes de lo que los crea, p. Ej. Solo de sus miembros) y los lectores (o fábricas si los datos no proviene de un Stream) que los crean, sino un término medio es un método lector estática:

abstract class Node 
{ 
    NodeKind kind; 
    public Node(NodeKind kind) { this.kind = kind; } 
    public NodeKind Kind { get { return kind; } } 

    static Node ReadFrom(Stream stream); 
} 

class FilesystemNode : Node 
{ 
    string filename; 
    public FilesystemNode(string filename) : Node(NodeKind.Filesystem) 
    { 
     this.filename = filename; 
    } 
    public string Filename { get { return filename; } } 

    static FilesystemNode ReadFrom(FileStream stream); 
} 

class CompositeNode : Node 
{ 
    Node[] values; 
    // I'm assuming IEnumerable<Node> here, but you can store whatever. 
    public CompositeNode(IEnumerable<Node> values) : Node(NodeKind.Composite) 
    { 
     this.values = values.ToArray(); 
    } 
    public IEnumerable<Node> { get { return filename; } } 
} 
Cuestiones relacionadas