Patrón de diseño de software Command para sistemas embebidos para los no iniciados
En este artículo revisaremos los conceptos básicos de este patrón —adaptados al contexto de nuestros sistemas embebidos— y luego construiremos un ejemplo práctico y sencillo: un LED controlado mediante comandos. A través de un ejemplo muy simple, intentaré explicar este patrón de forma clara y directa para que lo puedas adoptar de inmediato en tus propios proyectos.
Tabla de contenidos
- ¿Qué es y qué necesito?
- Patrón Command (Command Design Pattern)
- Metodología
- Ejemplo básico
- Código fuente y UML completo
- ¿Qué sigue?
- APÉNDICE: Código completo en caso de que no quieras pasar por Github
¿Qué es y qué necesito?
En nuestros sistemas embebidos siempre empezamos con algo simple: unos cuantos botones, un par de estados y acciones directas. Pero basta que el proyecto crezca un poco para que ese botón que antes sólo encendía un LED ahora deba hacer algo distinto según si una puerta automática está abriendo, cerrando, pausada o esperando la señal de un sensor. Y sin darnos cuenta, el código se convierte en un espaguetti de if–else que nadie quiere tocar. Todos hemos pasado por lo mismo, no me quieran mentir.
El problema no son los botones; sino que cada botón puede significar algo distinto según el estado del sistema. Cuando intentamos manejar todo con condicionales el código se convierte en una pesadilla: se dispersa, se duplica y se vuelve frágil.
Aquí es donde el patrón de diseño de software Command Pattern nos ofrece una alternativa elegante:
- Encapsula las acciones.
- Separa las decisiones del comportamiento.
- Permite mapear estados a comandos sin ramificaciones infinitas.
El resultado es un código fácil de entender, fácil de mantener y que escala de forma muy natural.
Y aunque este patrón suele mencionarse en el contexto de aplicaciones de escritorio, su utilidad en sistemas embebidos es mucho mayor de lo que uno podría pensar. Después de todo, en nuestro día a día todo gira alrededor de emitir y ejecutar comandos:
- Encender o apagar un LED.
- Ajustar la velocidad de un motor.
- Mover un actuador a una posición específica.
- Disparar una rutina de inicialización.
- Ejecutar una secuencia de comandos
Cada interacción —por simple que parezca— sigue el mismo patrón: alguien pide que algo ocurra, y algo en el sistema responde.
Si reducimos este mecanismo a sus elementos más básicos, siempre encontraremos los mismos tres actores:
- Invoker: es quien emite la orden (en el artículo lo llamo emisor).
- Command: es la orden encapsulada.
- Receiver: es quien finalmente ejecuta la acción.
En la práctica, estos tres suelen aparecer fuertemente acoplados. Es normal: funciona, es directo y fácil de programar, porque no hay que pensar mucho. Pero en cuanto el mismo emisor necesita producir acciones distintas según el contexto, o cuando distintas fuentes deben generar la misma orden, ese acoplamiento comienza a dejar de servir. Y si además necesitas crear una cadena de comandos… las pesadillas comienzan.
Los escenarios anteriores son todo menos teóricos y se nos presentan todos los días. Por ejemplo:
- Un mismo botón con múltiples comportamientos, dependiendo del estado del sistema: el botón A hace una cosa cuando está en el estado X, pero hace algo diferente cuando está en el estado Y.
- Un mismo comando emitido desde fuentes distintas: un botón físico, una interrupción o un mensaje recibido por Internet.
- Secuencias completas de acciones que deben ejecutarse en orden para un mismo periférico (una especie de macro instrucción).
- Deshacer la última acción, que se vuelve muy útil cuando una operación debe revertirse o cancelarse.
¿Reconoces alguno de estos casos? Si te resultan familiares, es porque ya estás a los dominios del patrón Command Pattern.
Patrón Command (Command Design Pattern)
El patrón Command nos permite separar a los tres actores antes mencionados (invoker, command y receiver) en objetos independientes. Su idea central es muy simple: el comando, la acción o la instrucción que queremos ejecutar se encapsula como un objeto. Nada más, nada menos.
Esta separación se lleva a cabo de la siguiente manera:
- El emisor (invoker) solicita la ejecución de un comando, pero no conoce ni la acción concreta ni al receptor que la llevará a cabo.
- El comando (command) encapsula la acción y solo conoce al receptor, nunca al emisor.
- El receptor (receiver) ejecuta la acción, pero no sabe quién la solicitó.
Gracias a esta división de responsabilidades tan clara como el agua, un mismo botón puede ejecutar distintas acciones, y una misma acción puede ser disparada por un botón, por un temporizador o por un mensaje recibido desde Internet.
La definición oficial de este patrón es:
“Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.”
Que más o menos traducido al español queda así:
“Encapsula una petición como un objeto, permitiendo parametrizar a los clientes con distintas solicitudes, almacenar o registrar solicitudes, y soportar operaciones reversibles.”
Si ponemos atención a la última parte de la definición notaremos que es posible (pero no forzoso) deshacer operaciones: deshaces la última suma, deshaces la última figura geométrica dibujada, deshaces el último movimiento del brazo robótico, etc. Es como el equivalente a la combinación de teclas Ctrl-Z en la computadora.
Y si nos enfocamos en otra parte de la definición –almacenar o registrar solicitudes– nos damos cuenta que podríamos construir listas de comandos. Interesante, ¿cierto?
Antes de terminar esta sección veamos el diagrama UML de este patrón (cortesía de la Wikipedia). No dejes que te intimide. Enseguida construiremos el nuestro propio, paso a paso, mientras desarrollamos el ejemplo base. Para cuando terminemos, quizás estés encaminado a ser un experto de este patrón (tomando en cuenta que en artículos posteriores estaremos estudiando aplicaciones y ejemplos un poco más complejos)
(Diagrama UML oficial, cortesía de Wikipedia)

Metodología
Frecuentemente percibimos al patrón Command como más complejo de lo que realmente es (y en general a todos los patrones). No porque sus elementos sean difíciles por separado, sino porque involucra varios actores trabajando en conjunto, cada uno con responsabilidades muy específicas. Esa interacción —y no la cantidad de código— es lo que puede generar confusión la primera vez que lo estudiamos.
Por eso, en esta serie de artículos avanzaremos paso a paso, con calma y en orden, tanto por ustedes como por mi “yo del futuro” que seguramente agradecerá tener este recorrido bien documentado.
En la entrega de hoy comenzaremos implementando el patrón en su forma más esencial: la versión clásica propuesta en el libro Design Patterns del GoF. La trabajaremos en C++ plano, sin extensiones modernas ni optimizaciones exóticas que nos distraigan. Nada de plantillas para eliminar polimorfismo dinámico o “modernismos” similares; primero lo clásico, lo directo, lo didáctico.
En el segundo artículo construiremos una macro, es decir, una secuencia de comandos que se ejecutan uno tras otro tan pronto como se activa un solo botón virtual.
Finalmente, en un tercer artículo veremos cómo deshacer acciones realizadas.
Ejemplo básico
Para explicar este patrón voy a usar un enfoque distinto al de los miles de tutoriales que abundan en Internet (y en los libros). En lugar de empezar por la definición del patrón, las jerarquías de clases o los diagramas UML —que suelen intimidar más de lo que ayudan—, vamos a partir directamente de la función main().
De esta forma vas a poder identificar a los actores en contexto, tal como se usan en una aplicación real. En otras palabras, en vez de seguir el enfoque tradicional bottom-up (del patrón hacia la aplicación), aquí trabajaremos top-down: de la aplicación hacia el patrón.
Al mismo tiempo, y siempre acompañados del código, iremos construyendo nuestro propio diagrama UML, elemento por elemento. Creo que este enfoque es más didáctico y mucho menos abrumador que comenzar mirando el diagrama oficial completo.
En nuestro ejemplo un botón ejecutará un comando. Luego, cargaremos un comando distinto en el mismo botón, y lo ejecutará sin ningún cambio adicional. Es la forma más simple de usar el patrón, pero nos abrirá la puerta a escenarios mucho más interesantes.
Cliente
En todos los patrones de diseño, incluyendo el que estamos analizando, siempre existe un elemento que crea y usa a los diferentes actores; este elemento puede ser una función suelta, una función miembro, una clase, etc. Cualquiera de estos elementos será el cliente del patrón. El cliente, para nuestro ejemplo, será la función main():
int main()
{
LedOnBoard led_on_board{ 13 };
Cmd_Led_On cmd_led_on{ &led_on_board };
Cmd_Led_Off cmd_led_off{ &led_on_board };
Controller button_A{ &cmd_led_on };
button_A.press_button();
button_A.set_command( &cmd_led_off );
button_A.press_button();
}
- Línea 3: creamos al receptor, que para este ejemplo será un LED con dos posibles acciones: encenderse o apagarse. Está conectado —hipotéticamente— al pin 13 de una tarjeta tipo Arduino. Lo que es valioso observar es que este es el hardware real donde la acción será ejecutada.
- Líneas 5 y 6: creamos dos comandos: uno para encender el LED y otro para apagarlo. Un detalle importante: cada comando recibe una referencia al receptor, lo que significa que el mismo objeto comando podría operar sobre otro LED distinto. Si tuviéramos un
LedOnBoard led2{3};, podríamos crearCmd_Led_Off cmd2( &led2 );y funcionaría igual. - Línea 8: creamos el controlador, que en este caso simula un botón físico. Durante la construcción establecemos que
cmd_led_onserá el comando que ejecutará al “presionarse” por primera vez. - Línea 10: simulamos una presión del botón. Como el comando asignado era
cmd_led_on, el LED se enciende. - Línea 12: aquí ocurre la magia: cambiamos dinámicamente el comando que el botón ejecutará la próxima vez que se presione. Ahora estará asociado a
cmd_led_off. - Línea 14: volvemos a presionar el botón. Y como era de esperarse, el LED se apaga. El mismo botón, comportamiento distinto, sin modificar su lógica interna. En inglés exclamaríamos: “Pretty cool, huh?”
Una posible salida de la ejecución del programa (simulando un LED en la PC) se observa así:

El diagrama UML, desde el punto de vista del cliente, se ve así:

Nota que en este momento no nos interesan los detalles de las clases de los diferentes actores que intervienen, sino simplemente las relaciones de creación y uso. A continuación comenzaremos a ver esos detalles.
Clase controladora Controller
La clase controladora —también conocida como invoker— es la encargada de emitir o disparar (trigger) los comandos. A diferencia de los commands y del receiver, la clase controladora no forma parte de ninguna jerarquía ni implementa ninguna interfaz. Es completamente tuya: la defines, la ajustas y la nombras conforme a las necesidades de tu proyecto.
Esto significa que su constructor, sus métodos y su API pública son totalmente flexibles: pueden recibir parámetros adicionales, manejar estado propio o incluir lógica específica del sistema.
class Controller
{
private:
ICommand* m_command{nullptr};
public:
Controller( ICommand* cmd = nullptr )
: m_command{cmd}
{}
void set_command( ICommand* cmd )
{
m_command = cmd;
}
void press_button()
{
if( m_command )
{
std::cout << "[Button was pressed!]->";
m_command->execute();
}
}
};
Sin embargo, hay tres elementos que toda clase controladora debe tener, sin importar el proyecto o la implementación:
- (Línea 4) Una variable miembro de tipo
ICommand*(o referencia equivalente) que almacena el comando que debe ejecutarse. Es el puente que conecta al controlador con la acción encapsulada. - (Línea 11) Un método para cambiar el comando. Este punto es clave: el patrón Command brilla precisamente porque permite reasignar dinámicamente qué acción debe ejecutarse.
- (Línea 16) Un método que dispare el comando, es decir, que invoque
execute()sobre el comando actualmente asociado. Este método, en nuestro ejemplo, es la “presión del botón”, el trigger que activa la acción real.
Además, debo aclarar dos detalles:
- No es obligatorio que el constructor reciba un comando inicial, pero suele ser útil cuando quieres que el controlador tenga un comportamiento definido desde el primer momento. Y es una buena práctica de programación inicializar a nuestros objetos.
- El texto de la línea 20 es meramente pedagógico para visualizar lo que sucede internamente; en una aplicación real podría omitirse o reemplazarse por un log.
Con estas piezas ya podemos visualizar en UML en qué punto del patrón nos encontramos:

Clase receptora
La clase receptora es quien finalmente ejecuta la acción solicitada por el comando. A diferencia del invoker, aquí sí existe una jerarquía de clases, porque necesitamos un contrato común que las futuras implementaciones tendrán que cumplir. Ese contrato es la interfaz ILED, que exige los métodos on() y off().
En esta arquitectura, el receptor es el experto en “hacer” algo real: encender un LED, mover un motor, activar un relevador o lo que corresponda en tu proyecto embebido.
struct ILED
{
virtual void on() = 0;
virtual void off() = 0;
virtual ~ILED() = default;
};
class LedOnBoard : public ILED
{
private:
uint8_t m_pin{0};
public:
LedOnBoard( uint8_t pin )
: m_pin{pin}
{}
virtual void on() override
{
std::cout << "LED on pin " << (int)m_pin << " is ON\n";
}
virtual void off() override
{
std::cout << "LED on pin " << (int)m_pin << " is OFF\n";
}
};
Veamos los puntos clave:
Interfaz ILED
- Líneas 1 a 6: Definimos la interfaz que obliga a cualquier tipo de LED (físico, virtual, multiplexado, remoto, etc.) a implementar
on()yoff(). Aunque no vamos a usar memoria dinámica, incluimos un destructor virtual —buena práctica al diseñar interfaces en C++— por si más adelante alguien extiende el patrón de forma más compleja.
Clase concreta LedOnBoard
- Línea 8: Creamos la clase concreta que hereda de ILED. Por su nombre, asumimos que controla un LED real conectado directamente a un pin del microcontrolador. Sin embargo, el punto clave del patrón es este: el comando no sabe ni le importa si el LED es real, virtual, simulado o parte de un expansor de pines. Sólo sabe que puede llamar a
on()yoff()y que un LED se va a encender o apagar, respectivamente. - Líneas 10 a 16: Declaramos las variables internas del LED (estado interno) y su constructor. Esta parte no es del patrón; es simplemente lo que tu hardware necesita para inicializarse.
- Líneas 18 a 26: Implementamos
on()yoff()con el código adecuado. En este ejemplo uso LEDs virtuales para facilitar la demostración, pero en un firmware real, aquí iría la escritura directa al registro GPIO correspondiente (o una llamada a las funciones de la HAL).
¿Cómo es el UML de esta sección? Así:

Interfaz ILED
¿Porqué necesitamos una interfaz ILED y no simplemente creamos una función “suelta” (en inglés freewheel) LedOnBoard()?
Porque la interfaz nos va a permitir manejar diferentes “tipos” de LEDs:
- On-board: LEDs conectados directamente a un pin del microcontrolador.
- Expansor: LEDs conectados a un expansor de pines (PCF8574, HC595, etc).
- Virtuales: LEDs simulados en la PC (a través de llamadas a
printf()ostd::cout). - Remotos: LEDs conectados en otra tarjeta y activados por alguna interfaz serial, como RS232, RS485, CANBus, Ethernet, etc.
Esta idea no está reflejada en este artículo porque he querido mantener simple el ejemplo, pero en todos los casos anteriores las clases deberán implementar las funciones on() y off(), con lo cual un intercambio de LEDs será mucho más fácil.
Si quieres saber más sobre Componentes intercambiables en sistemas embebidos, te invito a que leas el siguiente ARTÍCULO (pero primero termina este).
Un UML que represente a diferentes tipos de LEDs se ve así:

Comandos
Los comandos son el corazón del Command Pattern. Aquí es donde una acción —encender un LED, mover un motor, activar un relé— se encapsula como un objeto, totalmente independiente de quién la solicita y totalmente independiente de quién la ejecuta.
struct ICommand
{
virtual void execute() = 0;
virtual ~ICommand() = default;
};
class Cmd_Led_On : public ICommand
{
private:
ILED* m_led;
public:
// el comando recibe al receptor:
Cmd_Led_On( ILED* led )
: m_led{led}
{}
virtual void execute() override
{
m_led->on();
}
};
class Cmd_Led_Off : public ICommand
{
private:
ILED* m_led;
public:
// el comando recibe al receptor:
Cmd_Led_Off( ILED* led )
: m_led{led}
{}
virtual void execute() override
{
m_led->off();
}
};
La interfaz ICommand
- Líneas 1 a 5: Definimos la interfaz para todos los comandos. Tradicionalmente el método se llama
execute(), aunque el nombre no es obligatorio; lo importante es que todos los comandos respondan al mismo mensaje, sin importar qué acción ejecuten internamente.
Incluimos también un destructor virtual por buena práctica de programación, aunque no usaremos memoria dinámica. Sin este destructor, cualquier jerarquía de comandos no sería robusta.
Comando concreto: encender un LED
- Líneas 7 a 22: Aquí definimos una implementación concreta de
ICommand. - Línea 10: Declaramos una variable miembro
m_led, que almacena una referencia al receptor. Esto es crucial: el comando conoce a qué receptor enviará la orden, pero no conoce al emisor que lo activa, ni sabe nada del contexto donde se usa.
Esa independencia es, precisamente, lo que hace que un comando pueda ser reutilizado en distintos escenarios, emisores o arquitecturas.
- Líneas 14 a 16: El constructor obliga a recibir un receptor válido (esto es, del tipo
ILED). Para este ejemplo no podríamos recibir una referencia a motores o elefantes (a menos que un elefante sepa cómo responder a los mensajeson()yoff()). Sabemos que cualquier objeto que implemente a la interfazILEDresponderá a los mismos mensajes, sin sorpresas. - Líneas 18 a 21: Implementamos
execute(). Aquí ocurre la acción real: el comando toma la solicitud abstracta y la traduce en una llamada am_led.on().
Comando concreto: apagar un LED
- Líneas 24 a 39: De la misma forma definimos un comando para apagar el LED.
- Línea 37: Observa que la acción concreta (
off()) no ocurre en el comando, sino en el receptor. El comando sólo “dispara” la llamada apropiada. No manipula pines, no escribe en registros, no sabe si el LED es real, virtual o remoto.
¿Cómo es el UML de esta sección?

¿Qué hemos ganado con todo esto?
Un comando es ahora un objeto autónomo, portable, testable, reutilizable y asignable. Un mismo comando puede ser activado desde distintos emisores (botones, UART, WiFi, timers…), y un mismo emisor puede ejecutar distintos comandos con sólo cambiar la referencia.
Es aquí donde realmente comienza a sentirse la potencia del patrón.
Código fuente y UML completo
El código completo de este ejemplo lo puedes descargar desde AQUÍ. Quizás quieras repasarlo, parte por parte, junto a las diferentes etapas del diagrama UML.
Y hablando de éste, finalmente podemos regresar al diagrama completo, con todas sus partes y esperando que en este punto te resulte menos intimidante y más útil:

¿Qué sigue?
Como viste, el patrón de diseño de software Command Pattern no es solamente una “forma elegante” de encapsular acciones: es una estructura mental que te ayuda a mantener tus programas ordenados, modulares y fáciles de extender. Quizás ya lo habías usado sin llamarlo por su nombre —todos lo hacemos—, pero conocer el patrón te da un lenguaje común, una manera clara de comunicar tus ideas y una base sólida para construir sistemas más complejos.
En esta serie iremos paso a paso:
- Primero los fundamentos (que acabamos de ver).
- Luego la composición de comandos para crear macro–instrucciones.
- Y después la siempre útil capacidad de deshacer. Si trabajas con robótica, interfaces hombre–máquina o flujos de acciones secuenciales, te aseguro que te será especialmente valioso.
Si noto interés en esta serie, más adelante exploraremos variantes más avanzadas:
- Cómo implementar el patrón usando polimorfismo estático con CRTP.
- Cómo aprovechar características del Modern C++ para reducir código y maximizar la eficiencia.
Mi recomendación final es simple: adopta los patrones como una herramienta para pensar y estructurar tus aplicaciones. Más allá del lenguaje y más allá de la sintaxis, la verdadera ganancia está en cómo estos esquemas te ayudan a evitar diseños frágiles, a separar responsabilidades y a mantener uniformidad entre componentes:
- Los comandos siempre implementan
execute(). - Los receptores siempre exponen la misma interfaz.
- El controlador trabaja solo con objetos que cumplen el contrato.
Cuando las piezas encajan de forma tan precisa, tus programas podrán crecer sin convertirse en un rompecabezas. Y eso, tanto en desarrollo embebido, como en software de escritorio, vale más que bitcoins.
Nos vemos en el próximo artículo. Si tienes dudas, comentarios o tienes un caso de uso donde ya lo has implementado, házmelo saber.
Curso gratuito de FreeRTOS
Espero que esta entrada haya sido de tu interés. Si tienes dudas, preguntas o comentarios, déjamelos en la caja de comentarios. Si el artículo te sirvió de algo, podrías suscribirte a mi blog o compartir esta entrada con alguien que consideres que puede serle de ayuda también.
APÉNDICE: Código completo en caso de que no quieras pasar por Github
/* Copyright (C)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* 2025 - fjrg76 dot com
*/
// basic
struct ICommand
{
virtual void execute() = 0;
virtual ~ICommand() = default;
};
// receiver (abstract):
struct ILED
{
virtual void on() = 0;
virtual void off() = 0;
virtual ~ILED() = default;
};
class Cmd_Led_On : public ICommand
{
private:
ILED* m_led;
public:
// command takes the receiver:
Cmd_Led_On( ILED* led )
: m_led{led}
{}
virtual void execute() override
{
m_led->on();
}
};
class Cmd_Led_Off : public ICommand
{
private:
ILED* m_led;
public:
// command takes the receiver:
Cmd_Led_Off( ILED* led )
: m_led{led}
{}
virtual void execute() override
{
m_led->off();
}
};
#include <iostream>
class LedOnBoard : public ILED
{
private:
uint8_t m_pin{0};
public:
LedOnBoard( uint8_t pin )
: m_pin{pin}
{}
virtual void on() override
{
std::cout << "LED on pin " << (int)m_pin << " is ON\n";
}
virtual void off() override
{
std::cout << "LED on pin " << (int)m_pin << " is OFF\n";
}
};
class Controller
{
private:
ICommand* m_command{nullptr};
public:
Controller( ICommand* cmd = nullptr )
: m_command{cmd}
{}
void set_command( ICommand* cmd )
{
m_command = cmd;
}
void press_button()
{
if( m_command )
{
std::cout << "[Button was pressed!]->";
m_command->execute();
}
}
};
int main()
{
// create the commands receiver:
LedOnBoard led_on_board{ 13 };
// create the commands:
Cmd_Led_On cmd_led_on{ &led_on_board };
Cmd_Led_Off cmd_led_off{ &led_on_board };
// create the controller and pass the initial command:
Controller button_A{ &cmd_led_on };
// press button A and command cmd_led_on will fire:
button_A.press_button();
// assign a new command to button A:
button_A.set_command( &cmd_led_off );
// press button A and command cmd_led_off will fire:
button_A.press_button();
}
- Writing Scalable Firmware: Implementing the Command Pattern in C++ - diciembre 4, 2025
- Patrón de diseño de software Command para sistemas embebidos para los no iniciados - noviembre 30, 2025
- Breathe Life into Your Displays with the Arduino Print Class - marzo 17, 2025
Deja una respuesta
Lo siento, debes estar conectado para publicar un comentario.