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

  1. Patrones en el JDK
  2. Patrones en Spring
  3. Otras librerías y frameworks
  4. Cómo reconocer patrones al leer código ajeno
  5. Lo que las convenciones de nombres enseñan
  6. 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, sin getInstance() estático, sin estado global intocable en tests. La ConfiguracionPideYa de PideYa acabó así.
  • Factory por todas partes — BeanFactory/ApplicationContext son fábricas gigantes; la interfaz FactoryBean<T> te deja escribir la tuya; @Bean en una clase @Configuration es un Factory Method que el contenedor invoca por ti.
  • Proxy dinámico como columna vertebral — @Transactional, @Cacheable, @Async y 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 @Transactional no 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 al ObservadorPedido de PideYa (con bonus: @TransactionalEventListener para 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 HandlerAdapter de Spring MVC adaptan tipos de controlador heterogéneos; DispatcherServlet es la fachada de todo el procesamiento web.

Otras librerías y frameworks

  • Hibernate / JPA: Session/EntityManager como Facade del motor de persistencia; Proxy virtual en el lazy loading (accedes a pedido.getCliente() y un proxy carga la entidad justo entonces — y lanza LazyInitializationException fuera de sesión: el precio del proxy); SessionFactory es 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 → @AfterEach es Template Method (en JUnit 3 era literal: heredabas de TestCase y sobreescribías setUp()); los TestWatcher/listeners del runner son Observer; las anotaciones @ParameterizedTest con sus proveedores de argumentos, fábricas.
  • Android: View/ViewGroup es el Composite de Swing reencarnado; OnClickListener es Observer; LayoutInflater.from(context) y Fragment.newInstance(...) son fábricas; RecyclerView.Adapter es un Adapter entre tus datos y las vistas; LiveData/Flow observables 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:

  1. 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.
  2. La estructura del paquete. Una interfaz con muchas implementaciones hermanas (CalculoEnvio + PorDistancia + TarifaPlana...) grita Strategy o State; una interfaz + una sola implementación XxxImpl grita... otra cosa (lo veremos en antipatrones).
  3. El sufijo del nombre — la pista más rápida y la menos fiable (siguiente apartado).
  4. El comportamiento en runtime/debug. ¿El stack trace muestra clases $Proxy42 o EnhancerBySpringCGLIB? Proxy dinámico. ¿El objeto que recorres nunca expone su colección interna? Iterator haciendo su trabajo.
  5. 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 AdaptadorPayPal no 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 PedidoFactory a 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.of a ListFactory.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, Util no son patrones; y un *Factory puede 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.Observable está 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 BufferedReader delega en su Reader interno 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:

  1. HttpRequest.newBuilder().uri(uri).timeout(Duration.ofSeconds(5)).GET().build()
  2. Collections.unmodifiableList(pedidos) — devuelve una List que lanza excepción en add.
  3. Runtime.getRuntime()
  4. new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), UTF_8))
  5. 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

Módulo 2: Patrones Creacionales

Módulo 3: Patrones Estructurales

Módulo 4: Patrones de Comportamiento

Módulo 5: Aplicación de Patrones de Diseño

Módulo 6: Patrones de Diseño Avanzados

Módulo 7: Recursos Adicionales y Conclusión

© Copyright 2026. Todos los derechos reservados