Un videojuego tipo Shooter o RPG, donde:
-
El jugador puede cambiar el modo de juego (por ejemplo: Fácil, Normal o Difícil).
-
Además, el jugador tiene estados dinámicos durante la partida (Normal, Envenenado, Enfurecido, Muerto).
-
Queremos que ambos comportamientos puedan cambiar en tiempo de ejecución, sin romper el código principal del juego.
Strategy – “Estrategias de dificultad” El patrón Strategy nos permite cambiar el algoritmo de comportamiento sin modificar al jugador.
Por ejemplo:
- En modo Fácil, los enemigos hacen menos daño.
- En modo Difícil, los enemigos atacan más rápido y con más precisión.
Cada “modo de juego” es una estrategia diferente que puede intercambiarse en tiempo real.
-
Cambio de comportamiento en tiempo de ejecución:
-
Permite modificar cómo actúan los objetos sin alterar su estructura.
El jugador puede cambiar de modo o estado durante la ejecución del programa sin necesidad de reescribir código.
- Bajo acoplamiento
Las estrategias (modos de juego) y los estados del jugador están desacoplados del contexto principal. Esto facilita mantener y ampliar el código sin afectar otras partes del sistema.
- Cumplimiento del principio de abierto/cerrado (OCP)
Es posible agregar nuevos modos de juego o nuevos estados sin modificar las clases existentes, solo añadiendo nuevas implementaciones de las interfaces.
- Reutilización de código
Cada comportamiento está encapsulado en su propia clase, lo que permite reutilizar y combinar comportamientos según las necesidades del contexto.
- Mayor legibilidad y mantenimiento
Separar las responsabilidades en clases específicas evita código monolítico o condicionales extensos (if-else o switch), haciendo el sistema más claro y escalable.
- Extensibilidad del sistema
Se pueden añadir fácilmente nuevas estrategias (como un modo “experto”) o estados adicionales (como “congelado” o “invencible”) sin alterar el código existente.
#include <iostream>
#include <string>
using namespace std;
class Player {
private:
string modo; // Puede ser "facil", "normal", "dificil"
string estado; // Puede ser "normal", "envenenado", "muerto"
public:
Player() {
modo = "facil";
estado = "normal";
}
void cambiarModo(string nuevoModo) {
modo = nuevoModo;
}
void cambiarEstado(string nuevoEstado) {
estado = nuevoEstado;
}
void luchar() {
if (modo == "facil") {
cout << "[Modo Fácil] El enemigo ataca débilmente y con lentitud.\n";
} else if (modo == "normal") {
cout << "[Modo Normal] El enemigo ataca con fuerza moderada.\n";
} else if (modo == "dificil") {
cout << "[Modo Difícil] El enemigo ataca rápidamente y con gran daño.\n";
} else {
cout << "[Modo Desconocido] El enemigo no sabe qué hacer.\n";
}
}
void moverse() {
if (estado == "normal") {
cout << "El jugador se mueve normalmente.\n";
} else if (estado == "envenenado") {
cout << "El jugador se mueve lentamente (envenenado).\n";
} else if (estado == "muerto") {
cout << "El jugador no puede moverse (muerto).\n";
} else {
cout << "El jugador está en un estado desconocido.\n";
}
}
void mostrarEstado() {
if (estado == "normal") {
cout << "Estado: Saludable.\n";
} else if (estado == "envenenado") {
cout << "Estado: Envenenado, pierde salud lentamente.\n";
} else if (estado == "muerto") {
cout << "Estado: Muerto.\n";
} else {
cout << "Estado: Desconocido.\n";
}
}
};
int main() {
Player jugador;
cout << "=== Simulador de Juego (Código Espagueti) ===\n\n";
jugador.mostrarEstado();
jugador.luchar();
jugador.moverse();
cout << "\n--- Cambiando al modo Difícil ---\n";
jugador.cambiarModo("dificil");
jugador.luchar();
cout << "\n--- El jugador ha sido envenenado ---\n";
jugador.cambiarEstado("envenenado");
jugador.mostrarEstado();
jugador.moverse();
cout << "\n--- El jugador muere ---\n";
jugador.cambiarEstado("muerto");
jugador.mostrarEstado();
jugador.moverse();
jugador.luchar(); // El jugador sigue luchando aunque está muerto
cout << "\nFin de la simulación.\n";
return 0;
}Violación del SRP: La clase Player hace todo: maneja el estado, el modo y la lógica del juego.
Violación del OCP: Si se agrega un nuevo modo o estado, hay que modificar el código existente.
Falta de abstracción (violación del DIP): El código depende directamente de cadenas y condiciones, no de interfaces.
Complejidad condicional alta: Muchas estructuras if-else para controlar comportamientos.
Poca mantenibilidad y escalabilidad: Cada nuevo comportamiento aumenta el riesgo de errores.
#include <iostream>
#include <string>
using namespace std;
//
// ===== PATRÓN STRATEGY =====
// Permite cambiar el modo de juego (fácil, difícil, etc.) en tiempo de ejecución.
//
// Interfaz Strategy
class GameMode {
public:
virtual void enemyAttack() = 0;
virtual ~GameMode() {}
};
// Estrategias concretas
class EasyMode : public GameMode {
public:
void enemyAttack() override {
cout << "[Modo Fácil] El enemigo ataca débilmente y con lentitud.\n";
}
};
class NormalMode : public GameMode {
public:
void enemyAttack() override {
cout << "[Modo Normal] El enemigo ataca con fuerza moderada.\n";
}
};
class HardMode : public GameMode {
public:
void enemyAttack() override {
cout << "[Modo Difícil] El enemigo ataca rápidamente y con gran daño.\n";
}
};
//
// ===== PATRÓN STATE =====
// Controla el estado del jugador (normal, envenenado, muerto, etc.)
//
// Interfaz State
class PlayerState {
public:
virtual void move() = 0;
virtual void status() = 0;
virtual ~PlayerState() {}
};
// Estados concretos
class NormalState : public PlayerState {
public:
void move() override { cout << "El jugador se mueve normalmente.\n"; }
void status() override { cout << "Estado: Saludable.\n"; }
};
class PoisonedState : public PlayerState {
public:
void move() override { cout << "El jugador se mueve lentamente (envenenado).\n"; }
void status() override { cout << "Estado: Envenenado, pierde salud lentamente.\n"; }
};
class DeadState : public PlayerState {
public:
void move() override { cout << "El jugador no puede moverse (muerto).\n"; }
void status() override { cout << "Estado: Muerto.\n"; }
};
//
// ===== CLASE CONTEXTO =====
// Une ambos patrones: Strategy (modo de juego) + State (estado del jugador)
//
class Player {
private:
GameMode* mode; // Estrategia actual (modo de juego)
PlayerState* state; // Estado actual del jugador
public:
Player(GameMode* m, PlayerState* s) : mode(m), state(s) {}
void setMode(GameMode* m) { mode = m; }
void setState(PlayerState* s) { state = s; }
void fight() { mode->enemyAttack(); }
void move() { state->move(); }
void showStatus() { state->status(); }
};
//
// ===== FUNCIÓN PRINCIPAL =====
// Demostración didáctica: cambiar comportamientos en tiempo de ejecución.
//
int main() {
// Creamos estrategias (modos de juego)
EasyMode easy;
NormalMode normal;
HardMode hard;
// Creamos estados posibles del jugador
NormalState normalState;
PoisonedState poisoned;
DeadState dead;
// Creamos jugador inicial con modo fácil y estado normal
Player player(&easy, &normalState);
cout << "=== Simulador de Juego: Strategy + State ===\n\n";
player.showStatus();
player.fight();
player.move();
cout << "\n--- Cambiando al modo Difícil ---\n";
player.setMode(&hard);
player.fight();
cout << "\n--- El jugador ha sido envenenado ---\n";
player.setState(&poisoned);
player.showStatus();
player.move();
cout << "\n--- El jugador muere ---\n";
player.setState(&dead);
player.showStatus();
player.move();
player.fight(); // Aunque está muerto, el modo sigue activo
cout << "\nFin de la simulación.\n";
return 0;
}La aplicación combinada de los patrones Strategy y State se relaciona directamente con varios principios SOLID, ya que ambos patrones promueven una arquitectura limpia, extensible y de bajo acoplamiento.
- Cada clase tiene una única responsabilidad.
- Las estrategias (modos de juego) solo definen cómo se comportan los enemigos.
- Los estados del jugador solo controlan su comportamiento interno.
- La clase Player se limita a coordinar ambos.
- Esto evita clases con múltiples razones para cambiar.
- El sistema está abierto a la extensión pero cerrado a la modificación.
- Se pueden agregar nuevos modos de juego o nuevos estados creando clases que implementen las interfaces existentes, sin modificar el código base.
- Las clases derivadas pueden sustituir a sus clases base sin alterar la funcionalidad del programa.
- Cualquier GameMode (por ejemplo, EasyMode, HardMode) puede reemplazar a otra sin afectar al Player.
- Lo mismo ocurre con PlayerState.
- Las interfaces GameMode y PlayerState son específicas y pequeñas, cada una define solo los métodos necesarios para su función.
- Esto evita interfaces grandes o con métodos innecesarios.
- El Player depende de abstracciones (GameMode, PlayerState), no de implementaciones concretas.
- Esto permite cambiar fácilmente las estrategias o estados sin modificar la clase principal.