Cambiar o un diccionario al asignar a un nuevo objeto

12

Recientemente, he llegado a preferir mapear relaciones 1-1 usando en Dictionarieslugar de Switchdeclaraciones. Creo que es un poco más rápido de escribir y más fácil de procesar mentalmente. Desafortunadamente, cuando se asigna a una nueva instancia de un objeto, no quiero definirlo así:

var fooDict = new Dictionary<int, IBigObject>()
{
    { 0, new Foo() }, // Creates an instance of Foo
    { 1, new Bar() }, // Creates an instance of Bar
    { 2, new Baz() }  // Creates an instance of Baz
}

var quux = fooDict[0]; // quux references Foo

Dada esa construcción, desperdicié ciclos de CPU y memoria creando 3 objetos, haciendo lo que sus constructores pudieran contener, y solo terminé usando uno de ellos. También creo que mapear otros objetos fooDict[0]en este caso hará que hagan referencia a lo mismo, en lugar de crear una nueva instancia de Foolo previsto. Una solución sería usar una lambda en su lugar:

var fooDict = new Dictionary<int, Func<IBigObject>>()
{
    { 0, () => new Foo() }, // Returns a new instance of Foo when invoked
    { 1, () => new Bar() }, // Ditto Bar
    { 2, () => new Baz() }  // Ditto Baz
}

var quux = fooDict[0](); // equivalent to saying 'var quux = new Foo();'

¿Esto está llegando a un punto donde es demasiado confuso? Es fácil perderse eso ()al final. ¿O es el mapeo a una función / expresión una práctica bastante común? La alternativa sería usar un interruptor:

IBigObject quux;
switch(someInt)
{
    case 0: quux = new Foo(); break;
    case 1: quux = new Bar(); break;
    case 2: quux = new Baz(); break;
}

¿Qué invocación es más aceptable?

  • Diccionario, para búsquedas más rápidas y menos palabras clave (mayúsculas y minúsculas)
  • Switch: más comúnmente encontrado en el código, no requiere el uso de un objeto Func <> para la indirección.
KChaloux
fuente
2
sin la lambda, se devolverá la misma instancia cada vez que realice la búsqueda con la misma clave (como en fooDict[0] is fooDict[0]). tanto con el lambda y el interruptor este no es el caso
de trinquete monstruo
@ratchetfreak Sí, realmente me di cuenta de esto cuando estaba escribiendo el ejemplo. Creo que lo anoté en alguna parte.
KChaloux
1
Supongo que el hecho de que lo coloques explícitamente en una constante significa que necesitas que el objeto creado sea mutable. Pero si un día puede hacerlos inmutables, devolver el objeto directamente será la mejor solución. Puede poner el dict en un campo constante y solo incurrir en el costo de la creación una vez en toda la aplicación.
Laurent Bourgault-Roy

Respuestas:

7

Esa es una versión interesante del patrón de fábrica . Me gusta la combinación del Diccionario y la expresión lambda; me hizo mirar ese contenedor de una nueva manera.

Estoy ignorando la preocupación en su pregunta sobre los ciclos de CPU, como mencionó en los comentarios de que el enfoque no lambda no proporciona lo que necesita.

Creo que cualquiera de los enfoques (switch vs. Dictionary + lambda) va a estar bien. La única limitación es que al usar el Diccionario, está limitando los tipos de entradas que podría recibir para generar la clase devuelta.

El uso de una declaración de cambio le proporcionaría más flexibilidad en los parámetros de entrada. Sin embargo, si esto tiene que ser un problema, puede envolver el Diccionario dentro de un método y obtener el mismo resultado final.

Si es nuevo para su equipo, comente el código y explique lo que está sucediendo. Solicite una revisión del código del equipo, guíelos a través de lo que se hizo y hágales saber. Aparte de eso, se ve bien.


fuente
Desafortunadamente, a partir de hace aproximadamente un mes, mi equipo está formado exclusivamente por mí (el líder dejó de fumar). No pensé en su relevancia para el patrón de fábrica. Esa es una observación ordenada, en realidad.
KChaloux
1
@KChaloux: Por supuesto, si estuviera usando sólo el patrón Factory Method, el case 0: quux = new Foo(); break;se convierte en case 0: return new Foo();lo que es francamente tan fácil de escribir y mucho más fácil de leer que{ 0, () => new Foo() }
PDR
@pdr Ya se muestran algunos lugares en el código. Probablemente haya una buena razón para crear un método de fábrica en el objeto que inspiró esta pregunta, pero pensé que era lo suficientemente interesante como para preguntar por sí mismo.
KChaloux
1
@KChaloux: Confieso que no estoy interesado en la reciente obsesión por reemplazar el interruptor / caja por un diccionario. Todavía no he visto un caso en el que simplificar y aislar el interruptor en su propio método no hubiera sido más efectivo.
pdr
@pdr Obsession es una palabra fuerte para usar aquí. Más consideración al decidir cómo lidiar con las asignaciones únicas entre valores. Estoy de acuerdo en que en los casos en que se repite, es mejor aislar un método de creación.
KChaloux
7

C # 4.0 le ofrece la Lazy<T>clase, que es similar a su propia segunda solución, pero grita "Inicialización diferida" más explícitamente.

var fooDict = new Dictionary<int, Lazy<IBigObject>>()
{
    { 0, new Lazy(() => new Foo()) }, // Returns a new instance of Foo when invoked
    { 1, new Lazy(() => new Bar()) }, // Ditto Bar
    { 2, new Lazy(() => new Baz()) }  // Ditto Baz
}
Avner Shahar-Kashtan
fuente
Ooh, no lo sabía.
KChaloux
¡Oh eso es agradable!
Laurent Bourgault-Roy
2
Sin embargo, una vez que se invoca Lazy.Value, usa la misma instancia durante toda su vida. Vea Inicialización perezosa
Dan Lyons
Por supuesto, de lo contrario no sería una inicialización perezosa, solo reinicialización cada vez.
Avner Shahar-Kashtan
El OP afirma que lo necesita para crear nuevas instancias cada vez. La segunda solución con lambdas y la tercera solución con un interruptor hacen eso, mientras que la primera solución y la implementación Lazy <T> no lo hacen.
Dan Lyons
2

Estilísticamente creo que la legibilidad es igual entre ellos. Es más fácil hacer una inyección de dependencia con el Dictionary.

No olvide que debe verificar si la clave existe al usar el Dictionary, y debe proporcionar un respaldo si no es así.

Preferiría la switchdeclaración para rutas de código estático y las Dictionaryrutas de código dinámico (donde puede agregar o eliminar entradas). El compilador podría realizar algunas optimizaciones estáticas con el switchque no puede con el Dictionary.

Curiosamente, este Dictionarypatrón es lo que la gente a veces hace en Python, porque Python carece de la switchdeclaración. De lo contrario, usan cadenas if-else.

M. Dudley
fuente
1

En general, no preferiría ninguno de los dos.

Lo que sea que esté consumiendo esto debería funcionar con a Func<int, IBigObject>. Entonces la fuente de su mapeo puede ser un Diccionario o un método que tenga una declaración de cambio o una llamada al servicio web o alguna búsqueda de archivos ... lo que sea.

En cuanto a la implementación, preferiría el Diccionario ya que se refactoriza más fácilmente de 'diccionario de código duro, clave de búsqueda, resultado de retorno' a 'cargar diccionario desde archivo, clave de búsqueda, resultado de retorno'.

Telastyn
fuente