El patrón Decorator (también conocido como Wrapper o Envoltorio) es un patrón de diseño estructural que permite añadir funcionalidades y comportamientos adicionales a objetos o componentes existentes sin alterar su estructura ni modificar el código de la clase base.
1. El Propósito y el Problema
El Problema
Imagina que tienes una interfaz estándar o un componente básico en tu aplicación (por ejemplo, una etiqueta HTML <a> para renderizar hipervínculos).
A medida que el proyecto crece, descubres necesidades avanzadas:
- Algunos enlaces son términos del glosario técnico y deben mostrar un modal/tooltip flotante en lugar de cambiar de página.
- Otros enlaces son externos y deben abrirse obligatoriamente en una pestaña nueva (
target="_blank" rel="noopener noreferrer"). - Ciertos enlaces internos requieren seguimiento analítico (tracking) de clics.
Si intentaras modificar el renderizador base de Markdown o alterar el DOM directamente con manipulación imperativa, romperías el principio de Responsabilidad Única (SRP) y acoplarías la lógica de renderizado con la lógica de negocio.
2. La Solución Decorator
En lugar de alterar el componente original o crear múltiples componentes rígidos mediante herencia, envolvemos el elemento base dentro de un decorador inteligente.
El objeto decorador:
- Mantiene exactamente la misma interfaz o props que el elemento envuelto (
href,children,className, etc.). - Intercepta la llamada para evaluar reglas dinámicas.
- Añade el comportamiento extra (renderizando una ventana flotante o atributos adicionales) o delega la ejecución al elemento original.
- Cliente / Renderizador MDX: Solicita la creación o renderizado de un hipervínculo
<a>. MdxLink(Decorador): Evalúa la propiedadhref.- Ruta de Glosario (
/glossary/): Retorna el componenteGlossaryHoverenriquecido con ventana flotante. - Enlace Estándar: Renderiza el elemento
<a>nativo de HTML.
3. Ejemplo Real: Interceptador de Enlaces MDX (MdxLink)
En nuestra arquitectura de artículos con Next.js y MDXRemote, aplicamos este patrón para decorar dinámicamente las etiquetas <a>:
// src/components/shared/ui/MdxLink.jsx
import GlossaryHover from './GlossaryHover';
export default function MdxLink({ href, children, ...props }) {
// 1. Lógica decoradora: Intercepta enlaces al glosario
if (href && href.includes('/glossary/')) {
const term = href.split('/glossary/')[1]?.replace(/\/$/, '');
if (term) {
// Devuelve la variante enriquecida con tooltip flotante
return <GlossaryHover term={term}>{children}</GlossaryHover>;
}
}
// 2. Comportamiento base por defecto
return (
<a href={href} {...props}>
{children}
</a>
);
}Inyección en el Registrador de Componentes
En lugar de contaminar el componente PostArticle.jsx con condicionales de hipervínculos, inyectamos el decorador directamente en la lista de componentes reconocidos por MDXRemote:
// src/components/shared/post/PostArticle.jsx
import MdxLink from '../../shared/ui/MdxLink';
const components = {
pre: Pre,
a: MdxLink, // Decoramos la etiqueta <a> globalmente dentro de MDX
};
export default function PostArticle({ prefix, source }) {
return (
<article className={`${prefix}-article-content`}>
<MDXRemote source={source} components={components} />
</article>
);
}4. Ventajas de este Enfoque
- Extensibilidad sin Modificación (Open/Closed Principle): Puedes añadir soporte para enlaces de descargas, archivos PDF o tracking de analíticas dentro de
MdxLinksin tocar los archivos.mdxni el componentePostArticle. - Transparencia para los Autores: Los redactores siguen escribiendo sintaxis estándar de Markdown
[Texto](/ruta)sin preocuparse por componentes especiales. - Desacoplamiento Completo: La maquetación de la página y el procesador de Markdown permanecen limpios y centrados en su única responsabilidad.