Recientemente eliminé una respuesta mía de Java en Code Review , que comenzó así:
private Person(PersonBuilder builder) {
Detener. Bandera roja. Un PersonBuilder construiría una Persona; sabe sobre una persona. La clase Person no debería saber nada sobre un PersonBuilder, es solo un tipo inmutable. Ha creado un acoplamiento circular aquí, donde A depende de B, que depende de A.
La persona solo debe tomar sus parámetros; un cliente que esté dispuesto a crear una Persona sin construirlo debería poder hacerlo.
Fui abofeteado con un voto negativo y me dijeron que (citando) Bandera roja, ¿por qué? La implementación aquí tiene la misma forma que Joshua Bloch demostró en su libro "Effective Java" (elemento # 2).
Entonces, parece que la forma correcta de implementar un patrón de generador en Java es hacer que el generador sea un tipo anidado (esto no es de lo que se trata esta pregunta), y luego hacer el producto (la clase del objeto que se está construyendo ) depende del constructor , así:
private StreetMap(Builder builder) { // Required parameters origin = builder.origin; destination = builder.destination; // Optional parameters waterColor = builder.waterColor; landColor = builder.landColor; highTrafficColor = builder.highTrafficColor; mediumTrafficColor = builder.mediumTrafficColor; lowTrafficColor = builder.lowTrafficColor; }
La misma página de Wikipedia para el mismo patrón de Builder tiene una implementación muy diferente (y mucho más flexible) para C #:
//Represents a product created by the builder public class Car { public Car() { } public int Wheels { get; set; } public string Colour { get; set; } }
Como puede ver, el producto aquí no sabe nada acerca de una Builder
clase, y por lo que le importa, podría ser instanciado por una llamada de constructor directo, una fábrica abstracta, ... o un constructor, hasta donde yo entiendo, el producto de un patrón de creación nunca necesita saber nada sobre lo que lo está creando.
Se me ha servido el contraargumento (que aparentemente se defiende explícitamente en el libro de Bloch) de que un patrón de construcción podría usarse para reelaborar un tipo que tendría un constructor hinchado con docenas de argumentos opcionales. Entonces, en lugar de apegarme a lo que creía saber, investigué un poco en este sitio y descubrí que, como sospechaba, este argumento no es válido .
Entonces, ¿cuál es el trato? ¿Por qué idear soluciones sobredimensionadas para un problema que ni siquiera debería existir en primer lugar? Si sacamos a Joshua Bloch de su pedestal por un minuto, ¿podemos encontrar una sola razón buena y válida para acoplar dos tipos concretos y llamarlo una mejor práctica?
Todo esto apesta a programación de culto de carga para mí.
fuente
Respuestas:
No estoy de acuerdo con su afirmación de que es una bandera roja. También estoy en desacuerdo con su representación del contrapunto, que hay una forma correcta de representar un patrón de generador.
Para mí hay una pregunta: ¿Es el generador una parte necesaria de la API del tipo? Aquí, ¿el PersonBuilder es una parte necesaria de la API de Person?
Es completamente razonable que una clase de Persona con comprobación de validez, inmutable y / o encapsulada estrechamente se cree necesariamente a través de un Generador que proporciona (independientemente de si ese generador está anidado o adyacente). Al hacerlo, la Persona puede mantener todos sus campos privados o paquete privado y final, y también dejar la clase abierta para modificación si es un trabajo en progreso. (Puede terminar cerrado por modificación y abierto por extensión, pero eso no es importante en este momento, y es especialmente controvertido para los objetos de datos inmutables).
Si se da el caso de que una Persona se crea necesariamente a través de un PersonBuilder suministrado como parte de un diseño de API de nivel de paquete único, entonces un acoplamiento circular está bien y una bandera roja no está garantizada aquí. En particular, lo declaró como un hecho incontrovertible y no como una opinión o elección de diseño de API, por lo que puede haber contribuido a una mala respuesta. No te habría rechazado, pero no culpo a nadie más por hacerlo, y dejaré el resto de la discusión sobre "lo que justifica un voto negativo" al centro de ayuda y meta de la Revisión de Código . Los votos negativos suceden; No es una "bofetada".
Por supuesto, si desea dejar muchas formas abiertas para construir un objeto Person, entonces PersonBuilder podría convertirse en un consumidor o utilidadde la clase Persona, de la cual la Persona no debería depender directamente. Esta opción parece más flexible, ¿quién no querría más opciones de creación de objetos? . Si el Constructor está destinado a representar o reemplazar un constructor con muchos campos opcionales o un constructor abierto a modificación, entonces el constructor largo de la clase puede ser un detalle de implementación que mejor se deje oculto. (También tenga en cuenta que un generador oficial y necesario no le impide escribir su propia utilidad de construcción, solo que su utilidad de construcción puede consumir el generador como una API como en mi pregunta original anterior).
pd: Observo que la muestra de revisión de código que enumeró tiene una Persona inmutable, pero el contraejemplo de Wikipedia enumera un Coche mutable con captadores y establecedores. Puede ser más fácil ver la maquinaria necesaria omitida del automóvil si mantiene el lenguaje y los invariantes consistentes entre ellos.
fuente
String(StringBuilder)
constructor, que parece obedecer a ese "principio" en el que es normal que un tipo dependa de su constructor. Me quedé estupefacto cuando vi que el constructor se sobrecargaba. No es como siString
tuviera 42 parámetros de constructor ...toString()
Es universal.Entiendo lo molesto que puede ser cuando se usa 'apelación a la autoridad' en un debate. Los argumentos deben basarse en su propia OMI y, aunque no está mal señalar el consejo de una persona tan respetada, realmente no puede considerarse un argumento completo en sí mismo, ya que sabemos que el Sol viaja alrededor de la Tierra porque Aristóteles lo dijo. .
Dicho esto, creo que la premisa de su argumento es la misma. Nunca debemos acoplar dos tipos concretos y llamarlo una mejor práctica porque ... ¿Alguien lo dijo?
Para que el argumento de que el acoplamiento sea problemático sea válido, debe haber problemas específicos que cree. Si este enfoque no está sujeto a esos problemas, entonces no puede argumentar lógicamente que esta forma de acoplamiento es problemática. Entonces, si desea expresar su punto aquí, creo que debe mostrar cómo este enfoque está sujeto a esos problemas y / o cómo el desacoplamiento del constructor mejorará un diseño.
Por casualidad, me cuesta ver cómo el enfoque combinado creará problemas. Las clases están completamente acopladas, sin duda, pero el constructor existe solo para crear instancias de la clase. Otra forma de verlo sería como una extensión del constructor.
fuente
String
está haciendo con una sobrecarga del constructor tomando unStringBuilder
parámetro (aunque es discutible si aStringBuilder
es un constructor , pero eso es otra historia).VBE
simulacro, completo con ventanas, panel de código activo, un proyecto y un componente con un módulo que contiene la cadena que proporcionó. Sin él, no sé cómo podría escribir el 80% de las pruebas en Rubberduck , al menos sin apuñalarme en el ojo cada vez que las miro.Para responder por qué un tipo se combinaría con un generador, es necesario entender por qué alguien usaría un generador en primer lugar. Específicamente: Bloch recomienda usar un generador cuando tenga una gran cantidad de parámetros de constructor. Esa no es la única razón para usar un constructor, pero supongo que es la más común. En resumen, el constructor es un reemplazo para esos parámetros de constructor y, a menudo, el constructor se declara en la misma clase que está construyendo. Por lo tanto, la clase que encierra ya tiene conocimiento del constructor y pasarlo como argumento de constructor no cambia eso. Es por eso que un tipo se combinaría con su constructor.
¿Por qué tener una gran cantidad de parámetros implica que debería usar un constructor? Bueno, tener una gran cantidad de parámetros no es infrecuente en el mundo real, ni viola el principio de responsabilidad única. También apesta cuando tienes diez parámetros de cadena y estás tratando de recordar cuáles son cuáles y si es la quinta o la sexta cadena lo que debería ser nulo. Un constructor facilita la administración de los parámetros y también puede hacer otras cosas interesantes sobre las que debería leer el libro.
¿Cuándo utiliza un generador que no está asociado con su tipo? En general, cuando los componentes de un objeto no están disponibles de inmediato y los objetos que tienen esas piezas no necesitan saber sobre el tipo que está tratando de construir o si está agregando un generador para una clase sobre la que no tiene control .
fuente
La
Person
clase no sabe qué lo está creando. Solo tiene un constructor con un parámetro, ¿y qué? El nombre del tipo de parámetro termina con ... Generador que realmente no fuerza nada. Es solo un nombre después de todo.El constructor es un objeto de configuración glorificado 1 . Y un objeto de configuración es realmente solo agrupar propiedades que se pertenecen entre sí. Si solo se pertenecen el uno al otro con el propósito de pasar al constructor de una clase, ¡que así sea! Ambos
origin
ydestination
delStreetMap
ejemplo son de tipoPoint
. ¿Sería posible pasar las coordenadas individuales de cada punto al mapa? Claro, pero las propiedades de unaPoint
pertenecen juntas, además puedes tener todo tipo de métodos que ayudan con su construcción:1 La diferencia es que el constructor no es solo un objeto de datos simple, sino que permite estas llamadas de setter encadenadas. El
Builder
se preocupa principalmente por cómo construirse.Ahora agreguemos un poco de patrón de comando a la mezcla, porque si observa de cerca, el generador es en realidad un comando:
La parte especial para el constructor es que:
Lo que se pasa al
Person
constructor puede construirlo, pero no necesariamente tiene que hacerlo. Puede ser un simple objeto de configuración antiguo o un generador.fuente
No, están completamente equivocados y tienes toda la razón. Ese modelo de constructor es simplemente tonto. No es asunto de la persona de donde provienen los argumentos del constructor. El constructor es solo una conveniencia para los usuarios y nada más.
fuente
PersonBuilder
en los que de hecho es una parte esencial de laPerson
API. Tal como está, esta respuesta no discute su caso.