Cuando se utiliza el método de encadenamiento como:
var car = new Car().OfBrand(Brand.Ford).OfModel(12345).PaintedIn(Color.Silver).Create();
Puede haber dos enfoques:
Reutilice el mismo objeto, así:
public Car PaintedIn(Color color) { this.Color = color; return this; }
Cree un nuevo objeto de tipo
Car
en cada paso, así:public Car PaintedIn(Color color) { var car = new Car(this); // Clone the current object. car.Color = color; // Assign the values to the clone, not the original object. return car; }
¿El primero es incorrecto o es más bien una elección personal del desarrollador?
Creo que el primer enfoque puede causar rápidamente el código intuitivo / engañoso. Ejemplo:
// Create a car with neither color, nor model.
var mercedes = new Car().OfBrand(Brand.MercedesBenz).PaintedIn(NeutralColor);
// Create several cars based on the neutral car.
var yellowCar = mercedes.PaintedIn(Color.Yellow).Create();
var specificModel = mercedes.OfModel(99).Create();
// Would `specificModel` car be yellow or of neutral color? How would you guess that if
// `yellowCar` were in a separate method called somewhere else in code?
¿Alguna idea?
coding-style
readability
method-chaining
Arseni Mourzenko
fuente
fuente
var car = new Car(Brand.Ford, 12345, Color.Silver);
?Respuestas:
Pondría la API fluida a su propia clase "constructora" separada del objeto que está creando. De esa manera, si el cliente no quiere usar la API fluida, aún puede usarla manualmente y no contamina el objeto de dominio (adhiriéndose al principio de responsabilidad única). En este caso se crearía lo siguiente:
Car
cual es el objeto de dominioCarBuilder
que contiene la API fluidaEl uso sería así:
La
CarBuilder
clase se vería así (estoy usando la convención de nomenclatura de C # aquí):Tenga en cuenta que esta clase no será segura para subprocesos (cada subproceso necesitará su propia instancia de CarBuilder). También tenga en cuenta que, aunque la API fluida es un concepto realmente genial, probablemente sea excesivo con el fin de crear objetos de dominio simples.
Este acuerdo es más útil si está creando una API para algo mucho más abstracto y tiene una configuración y ejecución más compleja, por lo que funciona muy bien en pruebas unitarias y marcos DI. Puedes ver algunos otros ejemplos en la sección Java del artículo de Wikipedia sobre la interfaz fluida con persistencia, manejo de fechas y objetos simulados.
EDITAR:
Como se señaló en los comentarios; podría hacer que la clase Builder sea una clase interna estática (dentro de Car) y Car podría hacerse inmutable. Este ejemplo de dejar que el coche sea inmutable parece un poco tonto; pero en un sistema más complejo, donde absolutamente no desea cambiar el contenido del objeto que se construye, es posible que desee hacerlo.
A continuación se muestra un ejemplo de cómo hacer la clase interna estática y cómo manejar una creación de objeto inmutable que construye:
El uso sería el siguiente:
Edición 2: Pete en los comentarios hizo una publicación de blog sobre el uso de constructores con funciones lambda en el contexto de escribir pruebas unitarias con objetos de dominio complejos. Es una alternativa interesante para hacer que el constructor sea un poco más expresivo.
En el caso de
CarBuilder
que necesite tener este método en su lugar:Que se puede usar así:
fuente
build()
(oBuild()
), no el nombre del tipo que construye (Car()
en su ejemplo). Además, siCar
es un objeto verdaderamente inmutable (por ejemplo, todos sus campos lo sonreadonly
), incluso el constructor no podrá mutarlo, por lo que elBuild()
método se hace responsable de construir la nueva instancia. Una forma de hacer esto es tenerCar
un solo constructor, que tome un Constructor como argumento; entonces elBuild()
método puede simplementereturn new Car(this);
.Eso depende.
¿Es su automóvil una entidad o un objeto de valor ? Si el automóvil es una entidad, entonces la identidad del objeto es importante, por lo que debe devolver la misma referencia. Si el objeto es un objeto de valor, debe ser inmutable, lo que significa que la única forma es devolver una nueva instancia cada vez.
Un ejemplo de esto último sería la clase DateTime en .NET, que es un objeto de valor.
Sin embargo, si el modelo es una entidad, me gusta la respuesta de Spoike sobre el uso de una clase de constructor para construir su objeto. En otras palabras, ese ejemplo que diste solo tiene sentido en mi humilde opinión si el coche es un objeto de valor.
fuente
Cree un generador interno estático separado.
Utilice argumentos de constructor normales para los parámetros requeridos. Y api fluido para opcional.
No cree un nuevo objeto al configurar el color, a menos que cambie el nombre del método NewCarInColour o algo similar.
Haría algo como esto con la marca según sea necesario y el resto opcional (esto es java, pero el suyo parece javascript, pero bastante seguro de que son intercambiables con un poco de selección de liendres):
fuente
Lo más importante es que, sea cual sea la decisión que elija, se indica claramente en el nombre y / o comentario del método.
No hay un estándar, a veces el método devolverá un nuevo objeto (la mayoría de los métodos de String lo hacen) o lo devolverá para encadenar o para mejorar la memoria).
Una vez diseñé un objeto Vector 3D y para cada operación matemática tenía implementados ambos métodos. Por instante el método de escala:
fuente
scale
(el mutador) yscaledBy
(el generador).Veo algunos problemas aquí que creo que pueden ser confusos ... Su primera línea en la pregunta:
Está llamando a un constructor (nuevo) y a un método de creación ... Un método de creación () casi siempre sería un método estático o un método de creación, y el compilador debería detectarlo en una advertencia o error para informarle, ya sea De esta manera, esta sintaxis es incorrecta o tiene algunos nombres terribles. Pero más adelante, no usas ambos, así que veamos eso.
Sin embargo, de nuevo con create, solo que no con un nuevo constructor. La cosa es que creo que estás buscando un método copy () en su lugar. Entonces, si ese es el caso, y es solo un mal nombre, veamos una cosa ... que llamas mercedes.Paintedin (Color.Yellow) .Copy () - Debería ser fácil ver eso y decir que está siendo 'pintado' 'antes de ser copiado, solo un flujo normal de lógica, para mí. Así que pon la copia primero.
para mí, es fácil ver que estás pintando la copia, haciendo tu auto amarillo.
fuente
El primer enfoque tiene el inconveniente que usted menciona, pero siempre que lo deje claro en los documentos, cualquier codificador medio competente no debería tener problemas. Todo el código de encadenamiento de métodos con el que he trabajado personalmente ha funcionado de esta manera.
El segundo enfoque obviamente tiene el inconveniente de ser más trabajo. También debe decidir si las copias que devuelve serán copias superficiales o profundas: lo que es mejor puede variar de una clase a otra o de un método a otro, por lo que introducirá inconsistencia o comprometerá el mejor comportamiento. Vale la pena señalar que esta es la única opción para objetos inmutables, como cadenas.
Hagas lo que hagas, no mezclar y combinar en la misma clase!
fuente
Prefiero pensar como el mecanismo de "Métodos de extensión".
fuente
Esta es una variación de los métodos anteriores. Las diferencias son que hay métodos estáticos en la clase Car que coinciden con los nombres de los métodos en el Generador, por lo que no necesita crear explícitamente un Generador:
Puede usar los mismos nombres de método que usa en las llamadas del generador encadenado:
Además, hay un método .copy () en la clase que devuelve un generador rellenado con todos los valores de la instancia actual, por lo que puede crear una variación sobre un tema:
Finalmente, el método .build () del constructor verifica que se hayan proporcionado todos los valores requeridos y arroja si falta alguno. Puede ser preferible requerir algunos valores en el constructor del constructor y permitir que el resto sea opcional; en ese caso, querrías uno de los patrones en las otras respuestas.
fuente