Diseño Orientado a Objetos
Resumen
Este paquete contiene reglas de diseño para bases de código escritas en un estilo orientado a objetos, no consejos universales. Las convenciones escritas del propio proyecto prevalecen sobre el paquete. Sigue el modismo del módulo que se está modificando. Esta habilidad dimensiona la tarea y nombra la habilidad que corresponde.
Carga la habilidad que este índice nombra antes de responder: sus reglas y ejemplos viven allí, no aquí.
Cuánto diseño justifica una tarea
| Tarea | Usar | Omitir |
|---|---|---|
| Una respuesta usada una vez y luego desechada | Nada | Todo |
| Un método en una clase que existe | Su nombre y su contrato | Remodelación |
| Una funcionalidad dentro de un flujo existente | El modismo que usa ese flujo | Nuevos límites |
| Una nueva clase o módulo | El bucle de diseño | Nada |
| Un nuevo contexto | El bucle, luego sus límites | Nada |
| Un nuevo sistema desde cero | El bucle, contextos, luego estructura | Nada |
Antes de diseñar
Antes de diseñar, establece la puerta: una regla que el software debe seguir, con un ejemplo que acepta y uno que rechaza. Por ejemplo, "Un espacio puede contener como máximo una cita." Acepta reservar un segundo paciente en un espacio diferente y rechaza reservar un segundo paciente en el mismo espacio. Si puedes escribir la regla, comienza. Si no puedes, lo que falta no es diseño, y este paquete no puede proporcionarlo.
El bucle de diseño
- Di lo que el software debe hacer en las palabras del dominio (hablando el lenguaje del dominio), y encuentra el objeto que sabe lo que cada comportamiento necesita (asignando responsabilidades).
- Encuentra el límite de consistencia: qué debe ser verdadero junto en una sola transacción (diseñando agregados).
- Decide dónde vive cada regla: una entidad, un objeto de valor o un servicio de dominio (colocando la lógica de dominio).
- Diseña los mensajes a los que cada objeto responde (encapsulando estado y comportamiento), y haz que los estados ilegales sean imposibles de construir (haciendo imposibles los estados ilegales).
- Invierte las dependencias para que el dominio no sepa nada de la base de datos ni del framework (invirtiendo dependencias, estratificando el sistema).
- Nombra las cosas en las palabras del dominio, y comenta solo lo que el código no puede decir (nombrando clases y métodos, comentando lo que el código no puede decir).
- Verifica el resultado contra las señales de alerta a continuación (revisando el diseño de objetos).
Qué tan estrictamente se entiende una regla
Cada regla central en este paquete lleva una de tres etiquetas, y la etiqueta dice qué tan estrictamente se entiende.
- Regla. Romperla es un defecto. Ningún framework la excusa.
- Predeterminado. Haz esto a menos que se cumpla una condición establecida. La condición es su propia oración, nunca una cláusula final.
- Juicio. Dos respuestas son defendibles. La habilidad dice qué inclina la balanza.
Síntomas y habilidades
Los síntomas apuntan a la habilidad que los trata, y esta tabla crece a medida que llegan las habilidades.
| Síntoma | Habilidad |
|---|---|
| ¿Qué clase debería hacer esto? | Asignando responsabilidades |
| Esta clase hace demasiado | Asignando responsabilidades |
| Un cambio se propaga a través de muchas clases | Diagnosticando acoplamiento |
| ¿Qué tan unidas están estas dos partes? | Diagnosticando acoplamiento |
| ¿Es esto un objeto o solo datos? | Decidiendo objeto o datos |
| Nuestro modelo de dominio es anémico | Decidiendo objeto o datos |
| Otro código decide por este objeto | Encapsulando estado y comportamiento |
| Los setters permiten que este objeto rompa sus reglas | Encapsulando estado y comportamiento |
| Este objeto puede existir medio construido | Construyendo objetos válidos |
| Este constructor toma demasiados argumentos | Construyendo objetos válidos |
| Cada nuevo caso significa editar código en funcionamiento | Encapsulando lo que varía |
| ¿Cuánta flexibilidad debería tener esto? | Encapsulando lo que varía |
| ¿Es esto un valor o una entidad? | Modelando valores y entidades |
| Cadenas y números sin procesar llevan reglas de dominio | Modelando valores y entidades |
| ¿Debería extender esa clase o contenerla? | Eligiendo composición sobre herencia |
| Cambiar una clase base rompe sus subclases | Eligiendo composición sobre herencia |
| Una subclase rompe lo que promete su padre | Honrando la sustituibilidad |
| El código verifica qué subtipo contiene | Honrando la sustituibilidad |
| El mismo switch sobre un tipo sigue apareciendo | Reemplazando condicionales con polimorfismo |
| ¿Subclases o coincidencia de patrones? | Reemplazando condicionales con polimorfismo |
| El código alcanza a través de una cadena de objetos | Limitando lo que un objeto sabe |
| Esta clase solo reenvía llamadas | Limitando lo que un objeto sabe |
| ¿Cómo debería llamarse esta clase o método? | Nombrando clases y métodos |
| Nombres como Manager, Helper o Util | Nombrando clases y métodos |
| Los llamadores dependen de métodos de interfaz que nunca usan | Diseñando interfaces |
| ¿Interfaz o clase base abstracta? | Diseñando interfaces |
| El código de dominio depende de una base de datos o framework | Invirtiendo dependencias |
| ¿Dónde se crean y conectan los objetos? | Invirtiendo dependencias |
| Los llamadores saben cómo otra clase almacena o formatea datos | Ocultando la implementación |
| Una clase filtra sus errores o pasos internos | Ocultando la implementación |
| Las banderas o campos opcionales permiten combinaciones imposibles | Haciendo imposibles los estados ilegales |
| Un tipo admite valores que el dominio nunca tiene | Haciendo imposibles los estados ilegales |
| Las palabras del código difieren de las del personal | Hablando el lenguaje del dominio |
| Una palabra significa cosas diferentes para dos equipos | Hablando el lenguaje del dominio |
| ¿Qué objetos deben cambiar juntos? | Diseñando agregados |
| Los guardados chocan o una carga trae demasiado | Diseñando agregados |
| ¿Qué cambios de estado están permitidos? | Modelando ciclos de vida |
| Un objeto cancelado o cerrado cambia de nuevo | Modelando ciclos de vida |
| ¿Cómo deberían almacenarse y encontrarse los objetos de dominio? | Diseñando repositorios |
| El repositorio tiene un método find por pantalla | Diseñando repositorios |
| Los casos de uso repiten la misma verificación de negocio | Colocando la lógica de dominio |
| ¿Servicio de dominio, método de entidad o servicio de aplicación? | Colocando la lógica de dominio |
| Otras partes deben reaccionar después de que se guarda un cambio | Publicando eventos de dominio |
| Un mensaje salió para un cambio que se deshizo | Publicando eventos de dominio |
| ¿Dónde termina el modelo de un equipo y comienza el de otro? | Delimitando contextos |
| ¿Qué partes son centrales y cuáles podemos comprar? | Delimitando contextos |
| ¿De qué manera deberían depender las capas? | Estratificando el sistema |
| Una pantalla o trabajo no se puede probar sin su framework | Estratificando el sistema |
| Los módulos dependen entre sí en un ciclo | Organizando módulos |
| ¿Monolito modular o servicios separados? | Organizando módulos |
| Un cambio en nuestra API rompe las aplicaciones que la llaman | Cruzando límites del sistema |
| ¿Cómo deberíamos versionar esta API o mensaje? | Cruzando límites del sistema |
| La herramienta de mapeo da forma a nuestras clases de dominio | Domando la persistencia |
| Una lista ejecuta una consulta por fila | Domando la persistencia |
| Las pantallas necesitan datos con forma diferente al modelo | Separando lecturas de escrituras |
| ¿Modelo de lectura, proyección o event sourcing? | Separando lecturas de escrituras |
| ¿MVC, MVP o MVVM para esta pantalla? | Separando presentación del dominio |
| Una pantalla edita objetos de dominio directamente | Separando presentación del dominio |
| ¿Qué patrón de diseño se ajusta a este problema? | Eligiendo un patrón |
| Un patrón se agregó antes de su problema | Eligiendo un patrón |
| Estas clases de patrón podrían ser una función | Patrones que el lenguaje proporciona |
| ¿Visitor o un conjunto cerrado con coincidencia? | Patrones que el lenguaje proporciona |
| Un método devuelve null cuando no encuentra nada | Diseñando el comportamiento ante fallos |
| Los rechazos vuelven como códigos que los llamadores ignoran | Diseñando el comportamiento ante fallos |
| Los hilos comparten un objeto y pierden o mezclan sus cambios | Manejando el estado compartido |
| ¿Un bloqueo o un único propietario para este estado? | Manejando el estado compartido |
| Una llamada a otro sistema se cuelga o falla a mitad de camino | Haciendo el trabajo fiable |
| Un reintento hizo el trabajo dos veces | Haciendo el trabajo fiable |
| No podemos explicar un incidente a partir de nuestros registros | Diseñando qué registrar |
| ¿Qué deberíamos registrar, contar o auditar? | Diseñando qué registrar |
| Una revisión encontró una clase dios o lava flow | Reconociendo antipatrones |
| ¿Qué antipatrón deberíamos arreglar primero? | Reconociendo antipatrones |
| Las pruebas se rompen aunque el comportamiento no cambió | Probando código orientado a objetos |
| ¿Mock, stub o fake para este colaborador? | Probando código orientado a objetos |
| Las pruebas dependen del tiempo o valores aleatorios | Diseñando para la testabilidad |
| El código se hace público solo para que las pruebas puedan alcanzarlo | Diseñando para la testabilidad |
| ¿Cómo reestructuramos este código de manera segura? | Refactorizando hacia el diseño de objetos |
| Una refactorización creció demasiado para terminarla | Refactorizando hacia el diseño de objetos |
| Debemos cambiar código que no tiene pruebas | Trabajando con código heredado |
| Reemplazar un sistema antiguo pieza por pieza | Trabajando con código heredado |
| ¿Qué debería verificar esta revisión? | Revisando el diseño de objetos |
| ¿Qué hallazgos de la revisión bloquean una fusión? | Revisando el diseño de objetos |
| Nadie recuerda por qué el código tiene esta forma | Documentando decisiones de diseño |
| ¿Qué deberíamos dibujar, y a qué nivel? | Documentando decisiones de diseño |
| Los comentarios repiten el código o lo contradicen | Comentando lo que el código no puede decir |
| ¿Dónde explicamos por qué este código se ve extraño? | Comentando lo que el código no puede decir |
| Un gancho o marcador del framework contiene una regla de negocio | Trabajando dentro de un framework |
| ¿Hasta dónde deberíamos seguir al framework? | Trabajando dentro de un framework |
| Un cálculo también guarda, envía o cambia sus entradas | Aplicando técnicas funcionales |
| ¿Qué técnicas funcionales encajan en código de objetos? | Aplicando técnicas funcionales |
Habilidades por lenguaje
Una habilidad por lenguaje mantiene las reglas centrales en un lenguaje y nunca las reemplaza. Una habilidad base por lenguaje es para código sin framework.
oo-java, deobject-oriented-design-java: records, sealed types,equalsyhashCode, excepciones verificadas,Optionaly clases cerradas a la extensión.oo-kotlin, deobject-oriented-design-kotlin: data classes, sealed types, tipos anulables, delegación conbyy funciones de extensión.oo-typescript, deobject-oriented-design-typescript: identificadores marcados, estado#private, clases abstractas, decoradores, uniones discriminadas y una raíz de composición.oo-javascript, deobject-oriented-design-javascript: cierre de variables privadas, prototipos, funciones de fábrica y el patrón módulo.oo-python, deobject-oriented-design-python(planeado):@dataclass,@property,__slots__, protocolos ytyping.oo-csharp, deobject-oriented-design-csharp(planeado): records, tipos anulables, LINQ, propiedades de solo inicialización y clases selladas.oo-ruby, deobject-oriented-design-ruby(planeado): módulos,attr_reader,attr_accessor, bloques yStruct.oo-php, deobject-oriented-design-php(planeado): tipos declarados, readonly, enumeraciones, traits y union types.oo-swift, deobject-oriented-design-swift(planeado): structs, protocolos, enumeraciones con valores asociados,Codabley tipos opcionales.oo-go, deobject-oriented-design-go(planeado): interfaces, composición de structs, métodos de valor/receptor y tipos opacos.
Meta-reglas
- La identidad de un objeto no es su estado. Dos clientes con el mismo nombre y dirección siguen siendo dos clientes. Los valores son lo que son, no quiénes son.
- No expongas una decisión de infraestructura en el dominio. Sin excepciones.
- La persistencia es una consecuencia, no una intención de diseño. Las columnas de la base de datos no son atributos de dominio.
- Haz que los estados ilegales no sean representables. Si no puedes construirlo, no se filtrará en una prueba.
- Encapsula lo que varía. Pon tras una interfaz o clase base lo que podría cambiar; no pongas lo que es estable en el dominio.
- La herencia captura especialización, no reutilización. La reutilización pertenece a la composición.
- Un método no debería hacer ambas cosas: decidir y hacer. O despacha a otra cosa, o hace el trabajo ella misma.
- Depende de abstracciones. Las dependencias concretas impiden la prueba y el cambio.
- Los objetos se comunican a través de mensajes, no de getters. Preguntar por datos para decidir por otro objeto es código ajeno.
- Los nombres provienen del dominio. Si los desarrolladores y los expertos del negocio usan la misma palabra, es la palabra correcta.