2012-05-17 8 views
5

Dado el siguiente programa:El uso dinámico para establecer las propiedades dispares de no controlada (tercero) sellaron tipos

using System; 
using System.Collections.Generic; 

namespace ConsoleApplication49 
{ 
    using FooSpace; 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      IEnumerable<FooBase> foos = FooFactory.CreateFoos(); 

      foreach (var foo in foos) 
      {    
       HandleFoo(foo); 
      } 
     } 

     private static void HandleFoo(FooBase foo) 
     { 
      dynamic fooObject = foo; 
      ApplyFooDefaults(fooObject); 
     } 

     private static void ApplyFooDefaults(Foo1 foo1) 
     { 
      foo1.Name = "Foo 1"; 

      Console.WriteLine(foo1); 
     } 

     private static void ApplyFooDefaults(Foo2 foo2) 
     { 
      foo2.Name  = "Foo 2"; 
      foo2.Description = "SomeDefaultDescription"; 

      Console.WriteLine(foo2); 
     } 

     private static void ApplyFooDefaults(Foo3 foo3) 
     { 
      foo3.Name = "Foo 3"; 
      foo3.MaxSize = Int32.MaxValue; 

      Console.WriteLine(foo3); 
     } 

     private static void ApplyFooDefaults(Foo4 foo4) 
     { 
      foo4.Name  = "Foo 4"; 
      foo4.MaxSize  = 99999999; 
      foo4.EnableCache = true; 

      Console.WriteLine(foo4); 
     } 

     private static void ApplyFooDefaults(FooBase unhandledFoo) 
     { 
      unhandledFoo.Name = "Unhandled Foo"; 
      Console.WriteLine(unhandledFoo); 
     } 
    }  
} 

///////////////////////////////////////////////////////// 
// Assume this namespace comes from a different assembly 
namespace FooSpace 
{ 
    //////////////////////////////////////////////// 
    // these cannot be changed, assume these are 
    // from the .Net framework or some 3rd party 
    // vendor outside of your ability to alter, in 
    // another assembly with the only way to create 
    // the objects is via the FooFactory and you 
    // don't know which foos are going to be created 
    // due to configuration. 

    public static class FooFactory 
    { 
     public static IEnumerable<FooBase> CreateFoos() 
     { 
      List<FooBase> foos = new List<FooBase>(); 
      foos.Add(new Foo1()); 
      foos.Add(new Foo2()); 
      foos.Add(new Foo3()); 
      foos.Add(new Foo4()); 
      foos.Add(new Foo5()); 

      return foos; 
     } 
    } 

    public class FooBase 
    { 
     protected FooBase() { } 

     public string Name { get; set; } 

     public override string ToString() 
     { 
      return String.Format("Type = {0}, Name=\"{1}\"", this.GetType().FullName, this.Name); 
     } 
    } 

    public sealed class Foo1 : FooBase 
    { 
     internal Foo1() { } 
    } 

    public sealed class Foo2 : FooBase 
    { 
     internal Foo2() { } 

     public string Description { get; set; } 

     public override string ToString() 
     { 
      string baseString = base.ToString(); 
      return String.Format("{0}, Description=\"{1}\"", baseString, this.Description); 
     } 
    } 

    public sealed class Foo3 : FooBase 
    { 
     internal Foo3() { } 

     public int MaxSize { get; set; } 

     public override string ToString() 
     { 
      string baseString = base.ToString(); 
      return String.Format("{0}, MaxSize={1}", baseString, this.MaxSize); 
     } 
    } 

    public sealed class Foo4 : FooBase 
    { 
     internal Foo4() { } 

     public int MaxSize { get; set; } 
     public bool EnableCache { get; set; } 

     public override string ToString() 
     { 
      string baseString = base.ToString(); 
      return String.Format("{0}, MaxSize={1}, EnableCache={2}", baseString, 
                     this.MaxSize, 
                     this.EnableCache); 
     } 
    } 

    public sealed class Foo5 : FooBase 
    { 
     internal Foo5() { } 
    } 
    //////////////////////////////////////////////// 
} 

que produce el siguiente resultado:

Type = ConsoleApplication49.Foo1, Name="Foo 1" 
Type = ConsoleApplication49.Foo2, Name="Foo 2", Description="SomeDefaultDescription" 
Type = ConsoleApplication49.Foo3, Name="Foo 3", MaxSize=2147483647 
Type = ConsoleApplication49.Foo4, Name="Foo 4", MaxSize=99999999, EnableCache=True 
Type = ConsoleApplication49.Foo5, Name="Unhandled Foo" 
Press any key to continue . . . 

opté por usar dinámico aquí para evitar el siguiente:

  1. usando las declaraciones switch/if/else eg switch(foo.GetType().Name)
  2. declaraciones de comprobación de tipo explícitas, p. foo is Foo1
  3. declaraciones explícitas de fundición, p. (Foo1)foo

Debido a la conversión dynamic, el método correcto ApplyFooDefaults se invoca basándose en el tipo del objeto pasado a HandleFoo(FooBase foo). Cualquier objeto que no tenga un método de controlador ApplyFooDefaults apropiado, cae en el método "catch all", ApplyFooDefaults(FooBase unhandledFoo).

Una parte clave aquí es que FooBase y las clases derivadas representan tipos que están fuera de nuestro control y de los que no se puede derivar para agregar interfaces adicionales.

¿Es este un "buen" uso para dinámico o puede resolverse este problema de forma OOP sin agregar complejidad adicional dadas las restricciones y el hecho de que esto es solo para establecer valores de propiedad predeterminados en estos objetos?

* ACTUALIZADO *

Después de la respuesta de Bob Horn, me di cuenta de que mi situación no era completa. Restricciones adicionales:

  1. No puede crear los Foos directamente, tiene que usar FooFactory.
  2. No puede suponer el tipo Foo porque el tipo Foo se especifica en la configuración y se creó de forma reflexiva.

.

+1

@Jon Skeet - Lo oí hablar en Code Mash 2012 en Sandusky, OH. En su mayor parte, parecía que no le interesaba el uso de tipos dinámicos en ninguna situación. Me encantaría tener tus pensamientos sobre esta pregunta. –

+1

Creo que su código es muy, * muy * bueno como es. – Alex

+1

Creo que es posible que desee considerar el uso de genéricos si está configurado con el uso dinámico. Similar: http://stackoverflow.com/q/10132760/1026459. Nota: Jon Skeet proporciona la respuesta en este enlace. –

Respuesta

1

Bueno, inicialización de objetos individuo debe suceder en los constructores de los tipos. Si la fábrica no puede hacer eso y emite un objeto con un tipo de base solo entonces claramente está más allá de los patrones de OOP para inicializar los objetos en función del tipo.

Por desgracia, la detección del tipo de tiempo de ejecución es el camino a seguir y la dinámica simplemente lo hace, así que sí, su solución es muy bonita.(pero la lib de terceros no lo es, porque lo obliga a usar tipos dinámicos)

+0

@M. Stramm, creo que es un problema inherente a los tipos creados de manera reflexiva en función de la configuración. Donde el código no conoce el tipo concreto que se creará hasta el tiempo de ejecución. Ahora, si posee el código, sin duda puede establecer sus valores predeterminados en el constructor. Pero para las clases de proveedores de terceros, a menos que le den ganchos específicos, parece que una inspección dinámica en tiempo de ejecución del tipo, utilizando dinámicas o de otro tipo, es su única alternativa. – Jim

+0

@Jim De acuerdo. Básicamente es responsabilidad de la fábrica realizar la inicialización de objetos y el código de llamada no debe preocuparse por el tipo de objetos creados. Si la fábrica no puede hacer esto, usted está * forzado * a romper el patrón y usar la verificación de tipo de tiempo de ejecución. Entonces, lo que estaba tratando de decir es que no hay una manera más agradable de evitar esto porque la fábrica de terceros no juega limpio. –

+0

Voy a aceptar su respuesta en ausencia de cualquier otro que responda con cualquier enfoque OOP alternativo. – Jim

0

Una posible manera de hacer esto y evitar dinámico sería hacer las ApplyFooDefaults() métodos de extensión:

public static class FooExtensions 
{ 
    public static void ApplyFooDefaults(this Foo1 foo1) 
    { 
     foo1.Name = "Foo 1"; 

     Console.WriteLine(foo1); 
    } 

    public static void ApplyFooDefaults(this Foo2 foo2) 
    { 
     foo2.Name = "Foo 2"; 
     foo2.Description = "SomeDefaultDescription"; 

     Console.WriteLine(foo2); 
    } 

    public static void ApplyFooDefaults(this Foo3 foo3) 
    { 
     foo3.Name = "Foo 3"; 
     foo3.MaxSize = Int32.MaxValue; 

     Console.WriteLine(foo3); 
    } 

    public static void ApplyFooDefaults(this Foo4 foo4) 
    { 
     foo4.Name = "Foo 4"; 
     foo4.MaxSize = 99999999; 
     foo4.EnableCache = true; 

     Console.WriteLine(foo4); 
    } 

    public static void ApplyFooDefaults(this FooBase unhandledFoo) 
    { 
     unhandledFoo.Name = "Unhandled Foo"; 
     Console.WriteLine(unhandledFoo); 
    } 
} 

En algún punto de su programa, usted tiene que crear cada foo. Cuando lo haga, llamar a los métodos de extensión a continuación:

static void Main(string[] args) 
{ 
    List<FooBase> foos = new List<FooBase>(); 

    Foo1 foo1 = new Foo1(); 
    foo1.ApplyFooDefaults(); 
    foos.Add(foo1); 

    Foo2 foo2 = new Foo2(); 
    foo2.ApplyFooDefaults(); 
    foos.Add(foo2); 

    Foo3 foo3 = new Foo3(); 
    foo3.ApplyFooDefaults(); 
    foos.Add(foo3); 

    Foo4 foo4 = new Foo4(); 
    foo4.ApplyFooDefaults(); 
    foos.Add(foo4); 

    Foo5 foo5 = new Foo5(); 
    foo5.ApplyFooDefaults(); 
    foos.Add(foo5); 

    Console.WriteLine("Press any key to end."); 
    Console.ReadKey(); 
} 

enter image description here

+0

Olvidé un aspecto del problema, los Foos se crean a través de la reflexión también fuera de nuestro control (desde la configuración). Actualizaré el ejemplo para reflejar eso. – Jim

+0

Además, no sabe qué Foos se crean porque el tipo se especifica en la configuración, de forma hipotética. – Jim

+0

LOL. Simplemente vas a seguir agregando restricciones hasta que la única opción que queda sea dinámica, ¿no? :) –

Cuestiones relacionadas