2010-08-24 16 views
6

Cuando creo clases, los constructores simples tienden a ser la norma. En uno de mis proyectos actuales, una biblioteca de películas, tengo un objeto de dominio Movie. Cuenta con una serie de propiedades, lo que resulta en un constructor de la siguiente manera:Construir un objeto (algo) complejo

public Movie(string title, int year, Genre genre, int length, IEnumerable<string> actors) 
{ 
    _title = title; 
    _year = year; 
    _genre = genre; 
    _length = length; 
    _actors = new List<string>(actors); 
} 

Esto no es terrible, pero no es fácil tampoco. ¿Valdría la pena utilizar un método de fábrica (static Movie CreateMovie(...)) o quizás un generador de objetos? ¿Hay algún patrón típico para crear instancias de clases de dominio?

ACTUALIZACIÓN: gracias por las respuestas. Probablemente estaba pensando demasiado en el asunto inicialmente, aunque aprendí algunas cosas que serán útiles en situaciones más complejas. Mi solución ahora es tener el título como el único parámetro requerido, y el resto como parámetros nombrados/opcionales. Esta parece ser la forma ideal para construir este objeto de dominio.

+0

Su propia solución a su pregunta no debe publicarse como una actualización de su pregunta. Debería ser una respuesta, o en este caso, probablemente un comentario. –

Respuesta

4

Si está utilizando .NET 4.0, puede usar optional/named parameters para simplificar la creación de un objeto que acepta múltiples argumentos, algunos de los cuales son opcionales. Esto es útil cuando desea evitar muchas sobrecargas diferentes para proporcionar la información necesaria sobre el objeto.

Si no está en .NET 4,, puede utilizar el patrón Object Builder para ensamblar su tipo. El constructor de objetos requiere un poco de esfuerzo para implementarlo, y se mantiene sincronizado con su tipo, por lo que si hay suficiente valor para hacerlo, depende de su situación.

Creo que el patrón del generador es más efectivo cuando se ensamblan jerarquías, en lugar de un tipo con muchas propiedades. En este último caso, generalmente sobrecarga o parámetros opcionales/nombrados.

+0

Actualmente estoy usando 3.5, pero 4.0 es una opción. También le di una oportunidad a la idea del constructor, es un poco de trabajo, y parece ser excesivo en este caso. Aunque puedo ver que es útil en configuraciones aún más complejas, que usted mencionó. –

1

Has dado una buena respuesta a tu propia pregunta, es el patrón de fábrica. Con el patrón de fábrica no necesita grandes constructores para la encapsulación, puede configurar los miembros del objeto en su función de fábrica y devolver ese objeto.

1

Esto es perfectamente aceptable, en mi humilde opinión. Sé que a veces los métodos estáticos son mal vistos, pero típicamente dejo caer ese código en un método estático que devuelve una instancia de la clase. Normalmente solo hago eso para objetos que tienen permitido tener valores nulos.

Si los valores del objeto no pueden ser nulos, agréguelos como parámetros al constructor para que no quede ningún objeto no válido flotando.

3

Depende.

Si ese es el único constructor para esa clase, significa que todas las propiedades son necesarias para crear una instancia del objeto. Si eso se alinea con las reglas de su negocio, genial. Si no, podría ser un poco engorroso. Si, por ejemplo, desea sembrar su sistema con Películas pero no siempre tiene los Actores, podría encontrarse en un aprieto.

El método CreateMovie() que menciona es otra opción, en caso de que necesite separar el constructor interno del acto de crear una instancia de Película.

Tiene muchas opciones disponibles para organizar constructores. Utilice los que le permiten diseñar su sistema sin olores y muchos principios (DRY, YAGNI, SRP)

+0

"siembra tu sistema con películas pero no siempre tiene los actores" - excelente punto. Había pensado en omitir ese parámetro y usar un método 'AddActor (string name)'. –

+0

Estoy empezando a pensar en tener el título como el único ctor. param., y tiene el resto como propiedades configurables, por lo que puedo agregar películas fácilmente al sistema y completar los detalles más adelante. –

+0

Generalmente no me gusta poner propiedades en ningún constructor, por lo tanto, mantengo mis opciones abiertas. Es muy fácil manejar la asignación de propiedades usando la nueva sintaxis de inicialización del objeto C#: [new object() {fooProp = "1", barProp = "Hello"}] –

4

Sí, utilizar un método de fábrica es un patrón típico, pero la pregunta es: ¿por qué lo necesita? ? Esto es lo que Wikipedia says sobre métodos de fábrica:

Al igual que otros patrones de creación, tiene que ver con el problema de la creación de objetos (productos), sin especificar la clase exacta del objeto que se va a crear. El patrón de diseño de método de fábrica maneja este problema al definir un método separado para crear los objetos, que las subclases pueden anular para especificar el tipo de producto derivado que se creará.

Así, el patrón método de fábrica tendría sentido si desea volver subclases deMovie. Si esto no es (y no será) un requisito, reemplazar el constructor público por un método de fábrica en realidad no sirve para nada.

Para los requisitos establecidos en su pregunta, su solución se ve muy bien para mí: Todos los campos obligatorios se pasan como parámetros para el constructor. Si ninguno de sus campos es obligatorio, es posible que desee agregar un inicializador predeterminado y usar el C# object initializer syntax.

+0

Buenos puntos en el método de fábrica. Es una técnica de creación que conozco, así que pensé en tirarla a la palestra. Dada la cita, parece innecesario aquí. –

1

No veo nada mal con la interfaz de su constructor y no veo lo que un método estático le puede proporcionar. Tendré exactamente los mismos parámetros, ¿verdad?

Los parámetros no parecen opcionales, por lo que no hay forma de proporcionar una sobrecarga con menos o utilizar parámetros opcionales.

Desde el punto de vista de la persona que llama, se ve algo como esto:

Movie m = new Movie("Inception", 2010, Genre.Drama, 150, actors); 

El propósito de una fábrica es proporcionarle una instancia concreta personalizable de una interfaz, no sólo llamar al constructor para ti. La idea es que la clase exacta no está codificada en el momento de la construcción. ¿Esto es realmente mejor?

Movie m = Movie.Create("Inception", 2010, Genre.Drama, 150, actors); 

Me parece más o menos lo mismo. Lo único mejor es si Create() devolvió otras clases concretas que Movie.

Una cosa en que pensar es cómo mejorar esto para que el código de llamada sea fácil de entender. El problema más obvio para mí es que no es obvio qué significa 150 sin mirar el código de Movie. Hay algunas maneras de mejorar que si se quería:

  1. utilizar un tipo de duración de la película y la construcción de ese tipo en línea nueva MovieLength (150)
  2. Uso named parameters if you are using .NET 4.0
  3. (ver @ respuesta de Heinzi) utilizar inicializadores de objeto
  4. Utilice un fluent interface

Con una interfaz fluida, su llamada se vería

Movie m = new Movie("Inception"). 
    MadeIn(2010). 
    InGenre(Genre.Drama). 
    WithRuntimeLength(150). 
    WithActors(actors); 

Francamente, todo esto parece exagerado para su caso. Los parámetros con nombre son razonables si está utilizando .NET 4.0, porque no son mucho más código y mejorarían el código en la persona que llama.

1

No veo nada de malo en dejar el constructor público como está. Estas son algunas de las reglas que observo a la hora de decidir si utilizar un método de fábrica.

  • Utilice un método de fábrica cuando la inicialización requiera un algoritmo complejo.
  • Utilice un método de fábrica cuando la inicialización requiera una operación IO bound.
  • Utilice un método de fábrica cuando la inicialización pueda arrojar una excepción que no pueda protegerse en el momento del desarrollo.
  • Utilice un método de fábrica cuando pueda estar justificado un exceso de verbos para mejorar la legibilidad.

De acuerdo con mis propias reglas personales dejaría el constructor como está.

0

En cuanto a mí, todo depende de su modelo de dominio. Si su modelo de dominio le permite crear objetos simples, debe hacerlo.

Pero a menudo tenemos muchos objetos compuestos y la creación de cada uno individualmente es demasiado complicada. Es por eso que estamos buscando la mejor manera de encapsular la lógica de creación de objetos compuestos. En realidad, solo tenemos dos alternativas descritas anteriormente: "Método de fábrica" ​​y "Creador de objetos". Crear objetos a través del método estático parece un poco extraño porque colocamos la lógica de creación de objetos en el objeto. Object Builder, a su vez, parece complicado.

Creo que la respuesta está en las pruebas unitarias. Este es exactamente el caso cuando TDD sería bastante útil: hacemos nuestro modelo de dominio paso a paso y comprendemos la necesidad de la complejidad del modelo de dominio.

1

Si puede distinguir los miembros del núcleo de datos de los parámetros de configuración, cree un constructor que tome todos los miembros del núcleo y nada más (ni siquiera los parámetros de configuración con valores predeterminados — para legibilidad). Inicialice los parámetros de configuración para establecer los valores predeterminados (en el cuerpo del método) y proporcionar setters. En ese punto, un método de fábrica podría comprarle algo, si hay configuraciones comunes de su objeto que desee.

Mejor aún, si encuentra que tiene un objeto que toma una gran lista de parámetros, el objeto puede ser demasiado gordo. Has olido el hecho de que tu código puede necesitar ser refactorizado. Considera descomponer tu objeto. La buena literatura sobre OO defiende fuertemente los objetos pequeños (por ejemplo, Martin Fowler, Refactoring; Bob Martin, Clean Code). Fowler explica cómo descomponer objetos grandes. Por ejemplo, los parámetros de configuración (si los hay) pueden indicar la necesidad de más polimorfismo, especialmente si son booleanos o enumeraciones (refactorizando "Convertir condicional a polimorfismo").

Necesitaría ver la forma en que se usa su objeto antes de dar un consejo más específico. Fowler dice que las variables que se usan juntas deben convertirse en su propio objeto. Por lo tanto, para ilustrar, si se están calculando ciertas cosas en función del género, el año y la longitud, pero no los otros atributos, es posible que haya que desglosarlos en su propio objeto — reduciendo la cantidad de parámetros que deben pasa a tu constructor.

Cuestiones relacionadas