Los patrones de diseño son fundamentales para estructurar conceptos avanzados que son ignorados o no impuestos por el lenguaje, ya que lenguajes como C son principalmente una forma de expresar la estructura de un programa en texto, pero no dictan la estructura de más alto nivel que se debe expresar.

Los patrones de diseño se encargan de organizar conceptos de más alto nivel en arreglos y árboles (código, objetos, controladores de dispositivos, etc.). Proporcionan soluciones generalizadas y probadas a problemas recurrentes.

A continuación, se detalla cómo los patrones de diseño ayudan a estructurar estos conceptos avanzados:

1. Abstracción, Modularidad y Programación Orientada a Objetos (OO)

Aunque C es un lenguaje estructurado, los patrones permiten aplicar principios de diseño orientados a objetos y abstracción que normalmente solo se encuentran en lenguajes OO nativos:

 Implementación de Objetos: El Patrón Objeto es el patrón más importante en C, ya que permite separar módulos en objetos y evitar el uso de variables globales. Los datos relacionados se agrupan en estructuras (structs), y las funciones que operan en ellas reciben un puntero de contexto (self). Esto garantiza que el flujo de datos se produzca a lo largo del grafo de llamadas, lo que facilita la sincronización y la reentrada del código.

 Abstracción de Datos e Implementación Oculta: Los patrones estructuran conceptos de ocultamiento de información y abstracción:

    ◦ El Patrón Hardware Proxy encapsula todo el acceso a un dispositivo de hardware, ocultando detalles de codificación o interfaces.

    ◦ Los Objetos Opacos permiten ocultar la definición completa de la estructura de datos en el archivo de implementación (.c), exponiendo solo un puntero (handle) para su uso externo, lo cual proporciona encapsulación.

 Polimorfismo e Interfaces Abstractas: El lenguaje C carece de una forma incorporada para definir interfaces abstractas y polimorfismo. Esto se estructura mediante patrones como:

    ◦ El Patrón Container-of, que se usa para implementar interfaces abstractas y permitir la manipulación de diferentes objetos a través de una interfaz común de forma segura, como se ve frecuentemente en el kernel de Linux.

    ◦ El uso de punteros a funciones incrustados en structs permite implementar funciones virtuales para lograr polimorfismo y herencia (subclassing).

2. Concurrencia y Gestión de Recursos

Los patrones abordan problemas complejos de tiempo y concurrencia inherentes a los sistemas embebidos (como RTOS o sistemas bare metal):

 Planificación de Tareas: Definen cómo se ejecutarán las tareas concurrentes. El Patrón Cyclic Executive (Ejecutivo Cíclico) ofrece una planificación simple y de bajos recursos, mientras que el Patrón Static Priority (Prioridad Estática) se basa en el soporte de un RTOS para priorizar la respuesta a eventos urgentes.

 Serialización y Sincronización: Permiten estructurar la interacción segura entre tareas:

    ◦ El Patrón Critical Region (Región Crítica) estructura la protección de recursos deshabilitando el cambio de tareas temporalmente.

    ◦ El Patrón Guarded Call (Llamada Protegida) serializa el acceso a servicios compartidos utilizando semáforos mutex.

    ◦ El Patrón Queuing (Cola) utiliza la comunicación asíncrona a través de colas de mensajes para compartir información y sincronizar tareas, lo que evita problemas de exclusión mutua directa.

    ◦ El Patrón Rendezvous (Cita) se utiliza para sincronizar tareas bajo precondiciones complejas, reificando la estrategia de coordinación en un objeto explícito.

 Evitar Interbloqueos (Deadlock): Los patrones niegan las condiciones necesarias para que ocurra un deadlock:

    ◦ El Patrón Simultaneous Locking (Bloqueo Simultáneo) evita mantener recursos mientras se solicitan otros, garantizando que todos los recursos necesarios se bloqueen a la vez o ninguno.

    ◦ El Patrón Ordered Locking (Bloqueo Ordenado) elimina el deadlock al imponer una secuencia global en la que se deben adquirir los recursos, rompiendo la condición de espera circular.

3. Implementación de Máquinas de Estados Finitos (FSM)

Las FSM son la herramienta principal para modelar el comportamiento reactivo. Los patrones estructuran su implementación, especialmente en C donde conceptos como los AND-states (regiones ortogonales) no son nativos:

 Estrategias de Implementación: Patrones como el Patrón State Table (Tabla de Estados) o el Patrón State (Estado) organizan la lógica de transición y acción de las FSM.

 Manejo de Estados Ortogonales: El Patrón Decomposed And-State (Estado AND Descompuesto) aborda la complejidad de implementar AND-states (regiones de estado lógicamente independientes) descomponiendo el objeto dueño de la máquina de estados en objetos que gestionan las regiones ortogonales y se comunican entre sí.

4. Seguridad y Fiabilidad (Safety and Reliability)

Los patrones estructuran mecanismos para la detección de fallos y la redundancia en sistemas de alta fiabilidad, abordando preocupaciones que van más allá de la funcionalidad básica:

 Integridad de Datos en Memoria:

    ◦ El Patrón One’s Complement (Complemento a Uno) detecta la corrupción de bits de datos críticos almacenados en memoria al replicar los datos en formato complementario y verificar su consistencia.

    ◦ El Patrón CRC (Cyclic Redundancy Check) calcula un código de detección de errores de longitud fija para identificar la corrupción en conjuntos de datos más grandes.

 Canales de Procesamiento y Redundancia Arquitectónica: El Patrón Channel (Canal) define una estructura para el procesamiento de datos de extremo a extremo (desde la adquisición sensorial hasta la actuación). Este se utiliza como base para patrones de redundancia mayor:

    ◦ El Patrón Protected Single Channel (Canal Único Protegido) añade comprobaciones de validación en puntos clave de la cadena de transformación.

    ◦ El Patrón Dual Channel (Doble Canal) replica el canal para proteger contra fallos de punto único y puede permitir la continuación del servicio o la transición a un estado seguro.

En resumen, los patrones de diseño elevan el nivel de abstracción del desarrollador, proporcionando un vocabulario más amplio y expresivo para describir la arquitectura del sistema y facilitando la reutilización de soluciones probadas para optimizar criterios de diseño cruciales como el rendimiento, la mantenibilidad y la fiabilidad