Todo el curso hemos construido patrones sobre PideYa; esta lección invierte el telescopio: los patrones que llevas años usando sin saberlo. Cada vez que recorres una lista con for-each, envuelves un stream en un BufferedReader o dejas que Spring te inyecte un bean, estás consumiendo Iterator, Decorator y Singleton escritos por otros. Leer estos ejemplos tiene doble valor: confirman que el catálogo no es teoría académica sino la ingeniería de las librerías más usadas del mundo, y entrenan la habilidad más rentable de esta lección — reconocer un patrón al leer código ajeno a partir de sus pistas, empezando por los nombres. No hablaremos aquí de arquitectura (capas, microservicios, mensajería): eso es territorio del módulo 6; aquí miramos clases y APIs concretas.
Contenido
- Patrones en el JDK
- Patrones en Spring
- Otras librerías y frameworks
- Cómo reconocer patrones al leer código ajeno
- Lo que las convenciones de nombres enseñan
- Ejercicios y conclusión
Patrones en el JDK
La biblioteca estándar de Java es el mayor catálogo de patrones en producción del planeta:
| Patrón | Dónde vive en el JDK | La pista |
|---|---|---|
| Iterator | java.util.Iterator, Iterable, todo el for-each |
El patrón ascendió a sintaxis del lenguaje |
| Decorator | java.io: BufferedReader, GZIPOutputStream... |
Constructores que reciben otro stream del mismo tipo |
| Observer | Listeners de Swing (ActionListener), java.util.concurrent.Flow |
addXxxListener, subscribe |
| Factory Method | Integer.valueOf, List.of, Optional.of, Files.newBufferedReader |
Estáticos que devuelven el tipo... o un subtipo que tú no eliges |
| Builder | StringBuilder, HttpRequest.newBuilder(), Stream.Builder |
Métodos encadenables + build() final |
| Proxy | java.lang.reflect.Proxy (la base de medio ecosistema) |
El JDK trae el patrón como utilidad de serie |
| Strategy | Comparator en sort, ThreadFactory, UncaughtExceptionHandler |
El algoritmo viaja como argumento |
| Template Method | AbstractList, InputStream.read(byte[]) sobre read(), ClassLoader.loadClass |
Clase Abstract* con métodos que "rellenar" |
| Composite | Swing: Container es un Component que contiene Components |
El contenedor implementa la interfaz del contenido |
| Flyweight | La caché de Integer.valueOf (−128..127), el string pool |
Instancias compartidas para valores repetidos |
| Adapter | Arrays.asList, Collections.list(Enumeration), InputStreamReader |
Convierte una interfaz (array, Enumeration, bytes) en otra (List, Iterator, chars) |
| Prototype | Object.clone() / Cloneable |
El mecanismo existe... con las pegas que vimos en 02-06 |
El caso java.io merece zoom, porque es el mismo apilamiento que los extras de plato de PideYa:
// Tres decoradores apilados sobre un núcleo, componibles en cualquier orden razonable:
var lector = new BufferedReader( // añade buffering y readLine()
new InputStreamReader( // adapta bytes → caracteres (¡un Adapter en la pila!)
new FileInputStream("pedidos.csv"))); // el componente concreto
// Y el careo en vivo: Integer.valueOf es Factory Method CON Flyweight dentro
Integer a = Integer.valueOf(100);
Integer b = Integer.valueOf(100);
// a == b es true: valueOf devolvió la MISMA instancia cacheada (-128..127).
// Con new Integer(100) (deprecado) serían objetos distintos: la fábrica
// existe precisamente para poder decidir cosas así sin que el cliente lo sepa.Ese último comentario es la moraleja creacional entera del módulo 2: quien controla la creación controla el compartir, el cachear y el subtipo — por eso el JDK moderno (List.of, Optional.of) ya casi no te deja hacer new.
Patrones en Spring
Spring no "usa" patrones: es patrones ensamblados, y varios resuelven críticas que hicimos en el curso:
- Singleton por contenedor — el scope por defecto de un bean: una instancia... por
ApplicationContext, no por JVM. Es exactamente la alternativa que defendimos en 02-02: unicidad gestionada por quien inyecta, singetInstance()estático, sin estado global intocable en tests. LaConfiguracionPideYade PideYa acabó así. - Factory por todas partes —
BeanFactory/ApplicationContextson fábricas gigantes; la interfazFactoryBean<T>te deja escribir la tuya;@Beanen una clase@Configurationes un Factory Method que el contenedor invoca por ti. - Proxy dinámico como columna vertebral —
@Transactional,@Cacheable,@Asyncy la seguridad de método funcionan porque Spring envuelve tu bean en un proxy (JDK dinámico si hay interfaz, CGLIB si no) que intercepta la llamada, hace su magia y delega. Es el Proxy del módulo 3 industrializado — y explica el clásico "mi@Transactionalno funciona si me llamo a mí mismo": la auto-llamada no pasa por el proxy. - Template Method sin herencia —
JdbcTemplate,RestTemplate,TransactionTemplate: el esqueleto (abrir conexión, ejecutar, mapear, cerrar, traducir excepciones) es fijo; tu paso variable entra como lambda/callback. La variante por composición del ejercicio final de 04-11:
// El template fija el flujo (conexión, statement, bucle, cierre, excepciones);
// tú solo aportas el paso variable: cómo mapear una fila.
List<Pedido> pedidos = jdbcTemplate.query(
"SELECT * FROM pedidos WHERE estado = ?",
(fila, num) -> new Pedido(fila.getLong("id"), fila.getString("estado")), // tu "hook"
"EN_REPARTO");- Observer en los eventos —
ApplicationEventPublisher.publishEvent(...)+@EventListener: difusión a interesados anónimos, idéntica en intención alObservadorPedidode PideYa (con bonus:@TransactionalEventListenerpara escuchar "cuando el commit sea real"). - Strategy institucionalizado — inyectar una interfaz con N implementaciones (
List<ReglaDescuento>autowired, o elegir por@Qualifier) es Strategy con el contenedor como selector. - Adapter y Facade internos — los
HandlerAdapterde Spring MVC adaptan tipos de controlador heterogéneos;DispatcherServletes la fachada de todo el procesamiento web.
Otras librerías y frameworks
- Hibernate / JPA:
Session/EntityManagercomo Facade del motor de persistencia; Proxy virtual en el lazy loading (accedes apedido.getCliente()y un proxy carga la entidad justo entonces — y lanzaLazyInitializationExceptionfuera de sesión: el precio del proxy);SessionFactoryes fábrica hasta en el nombre; la caché de primer nivel funciona como Identity Map con sabor Flyweight: misma fila, misma instancia dentro de la sesión. - JUnit: el ciclo
@BeforeEach→ test →@AfterEaches Template Method (en JUnit 3 era literal: heredabas deTestCasey sobreescribíassetUp()); losTestWatcher/listeners del runner son Observer; las anotaciones@ParameterizedTestcon sus proveedores de argumentos, fábricas. - Android:
View/ViewGroupes el Composite de Swing reencarnado;OnClickListeneres Observer;LayoutInflater.from(context)yFragment.newInstance(...)son fábricas;RecyclerView.Adapteres un Adapter entre tus datos y las vistas;LiveData/Flowobservables por doquier. - Frontend (para el vistazo políglota): el patrón Observer domina — los hooks de React y los observables de RxJS son variaciones sobre "suscribirse a cambios"; Redux combina Command (acciones como objetos) con un único árbol de estado. Los patrones GoF son de diseño OO, pero sus intenciones cruzan lenguajes.
Cómo reconocer patrones al leer código ajeno
La habilidad práctica: abres un repositorio desconocido y quieres orientarte. Pistas por orden de fiabilidad:
- La firma antes que el nombre. Un constructor que recibe un objeto de su mismo supertipo → Decorator/Proxy/Adapter (mira si añade, controla o traduce). Un método que recibe una interfaz de un solo método "de hacer algo" → Strategy/callback. Estáticos que devuelven el propio tipo → fábrica. Encadenables +
build()→ Builder. - La estructura del paquete. Una interfaz con muchas implementaciones hermanas (
CalculoEnvio+PorDistancia+TarifaPlana...) grita Strategy o State; una interfaz + una sola implementaciónXxxImplgrita... otra cosa (lo veremos en antipatrones). - El sufijo del nombre — la pista más rápida y la menos fiable (siguiente apartado).
- El comportamiento en runtime/debug. ¿El stack trace muestra clases
$Proxy42oEnhancerBySpringCGLIB? Proxy dinámico. ¿El objeto que recorres nunca expone su colección interna? Iterator haciendo su trabajo. - La documentación de la clase. Las librerías serias nombran el patrón en el Javadoc ("This class implements the builder pattern") — el vocabulario compartido de 01-01 funcionando exactamente como prometía.
Regla de contraste: el nombre te da la hipótesis; la firma y el comportamiento la confirman. Un XxxFactory que solo tiene un getInstance() estático puede ser un Singleton disfrazado; un XxxManager puede ser cualquier cosa, incluido un problema.
Lo que las convenciones de nombres enseñan
| Sufijo / prefijo | Hipótesis de patrón | Ejemplos reales |
|---|---|---|
*Factory, *Supplier, of/valueOf/newXxx |
Factory Method / Abstract Factory | SessionFactory, ThreadFactory, List.of |
*Builder |
Builder | StringBuilder, UriComponentsBuilder |
*Adapter |
Adapter | RecyclerView.Adapter, HandlerAdapter |
*Proxy |
Proxy | java.lang.reflect.Proxy |
*Template |
Template Method (a menudo con callbacks) | JdbcTemplate, RestTemplate |
*Listener, *Observer, on*/add*Listener, subscribe |
Observer | ActionListener, @EventListener |
*Strategy, *Policy, *Resolver, Comparator |
Strategy | RetryPolicy, ViewResolver |
*Handler + campo next/successor |
Chain of Responsibility | filtros de servlet, pipelines |
*Command, *Action, *Task |
Command | acciones de Redux, Runnable como command mínimo |
*Visitor, métodos accept/visitXxx |
Visitor | FileVisitor, visitors de ASM y de los AST |
Abstract*, Base* |
Template Method probable | AbstractList |
*Facade, *Service (a veces), *Helper (ojalá) |
Facade | EntityManager en espíritu |
Tres lecciones que deja esta tabla:
- El vocabulario compartido funciona en ambas direcciones. Nombrar tu clase
AdaptadorPayPalno es pedantería: es documentación gratuita que cualquier lector con el catálogo descifra en un segundo. Es la ventaja de comunicación de 01-06, cobrada cada día. - El nombre es un contrato. Si llamas
PedidoFactorya algo que además envía emails, mientes al lector con el peor tipo de mentira: la que parece documentación. Nombra por el patrón solo cuando cumples su intención. - La ausencia de sufijo también informa. El JDK moderno prefiere
List.ofaListFactory.create: cuando el patrón es idiomático, el nombre del método basta. Los sufijos abundan donde el patrón necesita señalizarse — y sobran donde ya es cultura.
Errores Comunes y Consejos
- Fiarse solo del nombre.
Context,Manager,Helper,Utilno son patrones; y un*Factorypuede no serlo. Hipótesis por nombre, confirmación por firma. - Ver patrones donde hay coincidencia estructural. Que una clase envuelva a otra no la hace Decorator: pregunta por la intención (¿añade responsabilidades conservando la interfaz, o controla el acceso, o traduce?). El careo de los cuatro envoltorios aplica también leyendo código ajeno.
- Imitar la forma sin el porqué. Copiar
getInstance()de una librería sin heredar su problema (unicidad real) importa el coste sin el beneficio. Las librerías también arrastran decisiones históricas:java.util.Observableestá deprecado — hasta el JDK des-aplica patrones mal colocados. - Consejo: cuando descubras un patrón en una librería, léele el código fuente (está a un clic en el IDE). Ver cómo
BufferedReaderdelega en suReaderinterno enseña más Decorator que cualquier diagrama. - Consejo: en tu propio código, sé generoso con los nombres de patrón cuando la intención sea genuina — le regalas al siguiente lector el mapa que tú tuviste que reconstruir.
Ejercicios
Ejercicio 1: safari de patrones
Identifica el patrón (y la pista que lo delata) en cada fragmento real:
HttpRequest.newBuilder().uri(uri).timeout(Duration.ofSeconds(5)).GET().build()Collections.unmodifiableList(pedidos)— devuelve unaListque lanza excepción enadd.Runtime.getRuntime()new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), UTF_8))Files.walkFileTree(inicio, new SimpleFileVisitor<Path>() { ... })
Ejercicio 2: la pista engañosa
org.springframework.core.io.ResourceLoader tiene un solo método: Resource getResource(String location), y según el prefijo (classpath:, file:, https:) devuelve una implementación distinta de Resource. Su nombre dice "Loader". (a) ¿Qué patrón es realmente, y qué pista pesa más que el nombre? (b) ¿Qué pieza equivalente construimos en PideYa en el módulo 2?
Ejercicio 3: auditoría de nombres en PideYa
Repasa mentalmente estos nombres del curso y di, para cada uno, qué promete el nombre al lector y si cumple la intención del patrón: FachadaCheckout, RegistroNotificadores, NotificadorConReintentos, CentralReparto. ¿Cuál de los cuatro es el único cuyo nombre NO anuncia su patrón, y por qué es una decisión razonable?
Soluciones
Solución 1: (1) Builder — encadenables + build(). (2) Proxy de protección (envoltorio con la misma interfaz que controla el acceso: no añade conducta, la restringe — eso lo separa de Decorator). (3) Singleton clásico del JDK — getXxx() estático que devuelve la instancia única. (4) Decorator sobre Adapter: OutputStreamWriter adapta bytes→caracteres, PrintWriter decora con println y formato — la pila de java.io. (5) Visitor (con Template Method de regalo: SimpleFileVisitor da implementaciones por defecto que tú sobreescribes) recorriendo un Composite: el árbol de directorios.
Solución 2: (a) Factory Method: el cliente pide "un recurso para esta ubicación" y la implementación decide la clase concreta (ClassPathResource, FileSystemResource, UrlResource). La pista decisiva es la firma y el comportamiento — un método que devuelve una abstracción eligiendo el subtipo por ti — no el sufijo Loader. (b) El RegistroNotificadores de 02-03: misma idea, clave (canal / prefijo) → creador registrado.
Solución 3: FachadaCheckout promete Facade y lo cumple (punto único que orquesta un subsistema). RegistroNotificadores promete un registro de fábricas y lo cumple (clave → Supplier). NotificadorConReintentos no dice "Decorator"... y es la excepción razonable: nombra lo que aporta (reintentos) sobre la interfaz que conserva (Notificador), que es exactamente como se leen los decoradores (BufferedReader tampoco se llama ReaderDecorator); el patrón se delata por la firma — recibe y expone Notificador. CentralReparto tampoco dice "Mediator", pero "central" comunica la topología en estrella mejor que el nombre técnico. Moraleja: el nombre debe servir al lector; a veces la intención de dominio comunica más que la etiqueta del catálogo — mientras la firma confirme el patrón.
Conclusión
El catálogo dejó de ser un libro de 1994 para ser el plano de las herramientas que usas a diario: el JDK con sus fábricas, builders, decoradores e iteradores; Spring como ensamblaje industrial de Singleton-por-contenedor, proxies y templates; Hibernate, JUnit y Android repitiendo las mismas intenciones. Y te llevas el método de lectura: hipótesis por el nombre, confirmación por la firma y el comportamiento — con las convenciones de nombres como idioma compartido que tu propio código también debería hablar. Queda la pregunta inversa: tu código existente, el que no nació con patrones, ¿cómo se lleva hacia ellos sin romperlo? Tests como red, pasos pequeños y un catálogo de transformaciones: Refactorización Usando Patrones de Diseño.
Curso de Patrones de Diseño de Software
Módulo 1: Introducción a los Patrones de Diseño
- ¿Qué son los Patrones de Diseño?
- Historia y Origen de los Patrones de Diseño
- Principios de Diseño: SOLID y Otros Fundamentos
- UML Esencial para Entender Patrones
- Clasificación de los Patrones de Diseño
- Ventajas y Desventajas de Usar Patrones de Diseño
Módulo 2: Patrones Creacionales
- Introducción a los Patrones Creacionales
- Singleton
- Factory Method
- Abstract Factory
- Builder
- Prototype
- Comparativa y Elección de Patrones Creacionales
Módulo 3: Patrones Estructurales
- Introducción a los Patrones Estructurales
- Adapter
- Bridge
- Composite
- Decorator
- Facade
- Flyweight
- Proxy
- Comparativa y Elección de Patrones Estructurales
Módulo 4: Patrones de Comportamiento
- Introducción a los Patrones de Comportamiento
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
- Comparativa y Elección de Patrones de Comportamiento
Módulo 5: Aplicación de Patrones de Diseño
- Cómo Seleccionar el Patrón Adecuado
- Ejemplos Prácticos de Uso de Patrones
- Patrones de Diseño en Proyectos Reales
- Refactorización Usando Patrones de Diseño
- Antipatrones: Cuándo los Patrones se Vuelven un Problema
Módulo 6: Patrones de Diseño Avanzados
- Patrones de Diseño en Arquitecturas Modernas
- Patrones de Diseño en Microservicios
- Patrones de Diseño en Sistemas Distribuidos
- Patrones de Concurrencia
- Patrones de Diseño en Desarrollo Ágil
