Ir al contenido

Unidad 6

En la unidad anterior profundizamos en los pilares de la OOP en C++ y aprendimos a ser responsables del código que ponemos en producción. En esta unidad daremos un paso más: no solo escribiremos código que funcione, sino código que esté bien diseñado. Exploraremos tres patrones de diseño: Observer, Factory y State, que son soluciones comprobadas a problemas recurrentes en software orientado a objetos. El hilo conductor es el mismo de la unidad anterior: diseñar implica decidir con criterio, y ser responsable del diseño significa poder justificar cada decisión.


Criterio (peso)Cumple plenamente (5.0)Se cumple medianamente (4.0)Problemas importantes (3.0)Falta comprensión básica (2.0)No hay evidencia (0.0)
1. Evidencias de análisis en bitácora (40%)Presenta todas las evidencias solicitadas. El punto de inspección elegido es pertinente y revelador. Cada evidencia incluye una explicación precisa de qué se está observando y una justificación clara de por qué constituye evidencia de comprensión del patrón correspondiente.Evidencias presentes pero con 1–2 vacíos menores (ej. el punto de inspección es adecuado pero no se justifica, o la justificación del patrón es superficial).Faltan evidencias clave o los puntos de inspección elegidos no son informativos para el patrón que se intenta demostrar.Las imágenes no son del depurador, no corresponden a lo solicitado, o carecen de explicación y justificación. No hay prueba de análisis.No se entregaron evidencias o no se puede acceder a ellas.
Evaluación
2. Sustentación (60%)Responde con precisión conectando (a) lo que se ve, (b) cómo está diseñado, (c) por qué ese patrón y no otro. Usa su bitácora para justificar decisiones. Reconoce límites/errores y propone mejoras.Respuestas correctas con imprecisiones menores o justificación superficial. Usa parcialmente la bitácora.Responde solo “qué hizo” pero le cuesta explicar “cómo” o “por qué”. Necesita guía para conectar con su propia evidencia/bitácora.No logra responder de forma coherente o responde sin relación con lo presentado/documentado.No se entregaron evidencias o no se puede acceder a ellas.
Evaluación


Set: ¿Qué aprenderás en esta unidad? 💡

Sección titulada «Set: ¿Qué aprenderás en esta unidad? 💡»

Aprenderás tres patrones de diseño: Observer, Factory y State, que estructuran cómo los objetos se comunican, se crean y cambian de comportamiento. Más importante aún, aprenderás a justificar por qué se usa cada patrón: qué problema resuelve, qué alternativa evita y qué consecuencia tiene en la arquitectura del sistema. Usarás el depurador como microscopio de arquitectura para observar estos patrones en acción en tiempo de ejecución.

Caso de estudio: partículas con Observer, Factory y State

Sección titulada «Caso de estudio: partículas con Observer, Factory y State»

Crea un nuevo proyecto en openFrameworks, agrega el siguiente código, ejecútalo y explora la aplicación.

ofApp.h:

#pragma once
#include "ofMain.h"
#include <string>
#include <vector>
class Observer {
public:
virtual ~Observer() = default;
virtual void onNotify(const std::string & event) = 0;
};
class Subject {
public:
void addObserver(Observer * observer);
void removeObserver(Observer * observer);
protected:
void notify(const std::string & event);
private:
std::vector<Observer *> observers;
};
class Particle;
class State {
public:
virtual ~State() = default;
virtual void update(Particle * particle) = 0;
virtual void onEnter(Particle * particle) { }
virtual void onExit(Particle * particle) { }
};
class Particle : public Observer {
public:
Particle();
~Particle() override;
Particle(const Particle &) = delete;
Particle & operator=(const Particle &) = delete;
void update();
void draw();
void onNotify(const std::string & event) override;
void setState(State * newState);
ofVec2f position;
ofVec2f velocity;
float size;
ofColor color;
private:
void keepInsideWindow();
State * state;
};
class NormalState : public State {
public:
void update(Particle * particle) override;
void onEnter(Particle * particle) override;
};
class AttractState : public State {
public:
void update(Particle * particle) override;
};
class RepelState : public State {
public:
void update(Particle * particle) override;
};
class StopState : public State {
public:
void update(Particle * particle) override;
};
class ParticleFactory {
public:
static Particle * createParticle(const std::string & type);
};
class ofApp : public ofBaseApp, public Subject {
public:
~ofApp() override;
void setup() override;
void update() override;
void draw() override;
void keyPressed(int key) override;
private:
std::vector<Particle *> particles;
};

ofApp.cpp:

#include "ofApp.h"
#include <algorithm>
void Subject::addObserver(Observer * observer) {
if (!observer) return;
if (std::find(observers.begin(), observers.end(), observer) == observers.end()) {
observers.push_back(observer);
}
}
void Subject::removeObserver(Observer * observer) {
if (!observer) return;
observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
}
void Subject::notify(const std::string & event) {
for (Observer * observer : observers) {
observer->onNotify(event);
}
}
Particle::Particle()
: state(nullptr) {
position = ofVec2f(ofRandomWidth(), ofRandomHeight());
velocity = ofVec2f(ofRandom(-0.5f, 0.5f), ofRandom(-0.5f, 0.5f));
size = ofRandom(2.0f, 5.0f);
color = ofColor(255);
state = new NormalState();
state->onEnter(this);
}
Particle::~Particle() {
if (state) {
state->onExit(this);
delete state;
state = nullptr;
}
}
void Particle::setState(State * newState) {
if (state) {
state->onExit(this);
delete state;
}
state = newState;
if (state) {
state->onEnter(this);
}
}
void Particle::update() {
if (state) {
state->update(this);
}
keepInsideWindow();
}
void Particle::draw() {
ofPushStyle();
ofSetColor(color);
ofDrawCircle(position, size);
ofPopStyle();
}
void Particle::onNotify(const std::string & event) {
if (event == "attract") {
setState(new AttractState());
} else if (event == "repel") {
setState(new RepelState());
} else if (event == "stop") {
setState(new StopState());
} else if (event == "normal") {
setState(new NormalState());
}
}
void Particle::keepInsideWindow() {
const float W = static_cast<float>(ofGetWidth());
const float H = static_cast<float>(ofGetHeight());
if (position.x < 0.0f) { position.x = 0.0f; velocity.x *= -1.0f; }
else if (position.x > W) { position.x = W; velocity.x *= -1.0f; }
if (position.y < 0.0f) { position.y = 0.0f; velocity.y *= -1.0f; }
else if (position.y > H) { position.y = H; velocity.y *= -1.0f; }
}
void NormalState::onEnter(Particle * particle) {
particle->velocity.set(ofRandom(-0.5f, 0.5f), ofRandom(-0.5f, 0.5f));
}
void NormalState::update(Particle * particle) {
particle->position += particle->velocity;
}
static void steer(Particle * particle, const ofVec2f & toward, float accel, float vmax, float posScale) {
ofVec2f dir = toward - particle->position;
float len = dir.length();
if (len > 1e-6f) { dir /= len; particle->velocity += dir * accel; }
particle->velocity.limit(vmax);
particle->position += particle->velocity * posScale;
}
void AttractState::update(Particle * particle) {
ofVec2f mouse(ofGetMouseX(), ofGetMouseY());
steer(particle, mouse, 0.05f, 3.0f, 0.2f);
}
void RepelState::update(Particle * particle) {
ofVec2f mouse(ofGetMouseX(), ofGetMouseY());
ofVec2f away = particle->position - mouse;
float len = away.length();
if (len > 1e-6f) { away /= len; particle->velocity += away * 0.05f; }
particle->velocity.limit(3.0f);
particle->position += particle->velocity * 0.2f;
}
void StopState::update(Particle * particle) {
particle->velocity *= 0.80f;
if (particle->velocity.lengthSquared() < 1e-4f) { particle->velocity.set(0.0f, 0.0f); }
particle->position += particle->velocity;
}
Particle * ParticleFactory::createParticle(const std::string & type) {
Particle * particle = new Particle();
if (type == "star") {
particle->size = ofRandom(2.0f, 4.0f);
particle->color = ofColor(255, 0, 0);
} else if (type == "shooting_star") {
particle->size = ofRandom(3.0f, 6.0f);
particle->color = ofColor(0, 255, 0);
particle->velocity *= 3.0f;
} else if (type == "planet") {
particle->size = ofRandom(5.0f, 8.0f);
particle->color = ofColor(0, 0, 255);
}
return particle;
}
ofApp::~ofApp() {
for (Particle * p : particles) { removeObserver(p); delete p; }
particles.clear();
}
void ofApp::setup() {
ofBackground(0);
particles.reserve(100 + 5 + 10);
for (int i = 0; i < 100; ++i) {
Particle * p = ParticleFactory::createParticle("star");
particles.push_back(p); addObserver(p);
}
for (int i = 0; i < 5; ++i) {
Particle * p = ParticleFactory::createParticle("shooting_star");
particles.push_back(p); addObserver(p);
}
for (int i = 0; i < 10; ++i) {
Particle * p = ParticleFactory::createParticle("planet");
particles.push_back(p); addObserver(p);
}
}
void ofApp::update() {
for (Particle * p : particles) { p->update(); }
}
void ofApp::draw() {
for (Particle * p : particles) { p->draw(); }
}
void ofApp::keyPressed(int key) {
switch (key) {
case 's': notify("stop"); break;
case 'a': notify("attract"); break;
case 'r': notify("repel"); break;
case 'n': notify("normal"); break;
default: break;
}
}

Investigación del patrón Observer

🧐🧪✍️ Coloca un breakpoint dentro de Subject::notify. Cuando se dispare al presionar una tecla, observa en el depurador el vector observers: ¿Cuántos elementos tiene? ¿Qué tipo de objetos son? ¿A qué direcciones de memoria apuntan?

🧐🧪✍️ Ahora coloca un breakpoint en Particle::onNotify. ¿Cuál es la dirección de memoria del objeto this que recibe la llamada? ¿Puedes encontrar esa misma dirección en el vector observers que observaste antes? ¿Qué concluyes sobre cómo el Subject sabe a quién notificar?

Investigación del patrón State

🧐🧪✍️ Coloca un breakpoint en Particle::setState. Observa en memoria el puntero state antes y después de la asignación: ¿Qué dirección tenía? ¿Qué dirección tiene ahora? ¿qué le pasó al objeto de estado anterior?

🧐🧪✍️ En la unidad anterior estudiaste la _vtable y el despacho dinámico. Ahora conéctalo con el patrón State: coloca breakpoints en NormalState::update y en AttractState::update. Presiona n y luego a. ¿A cuál llega el depurador primero en cada caso? ¿Cómo demuestra esto que el patrón State usa polimorfismo para cambiar el comportamiento en tiempo de ejecución?

Investigación del patrón Factory

🧐🧪✍️ Coloca un breakpoint en ParticleFactory::createParticle. Observa el parámetro type y el objeto Particle* que se retorna. Luego, en ofApp::setup, observa el vector particles justo después de las llamadas a la factory. ¿Qué relación tienen las direcciones de los objetos creados con los elementos del vector?


🧐🧪✍️ Usando excalidraw, dibuja un diagrama de flujo de mensajes que muestre la cadena completa: keyPressednotifyonNotifysetStatenueva _vtable activa. Indica en qué punto interviene cada patrón (Observer, State) y dónde la Factory ya cumplió su rol (en el setup).

🧐🧪✍️ En el mismo diagrama, agrega una nota que responda: ¿Qué pasaría si no usaras el patrón Observer y en cambio ofApp::update recorriera todas las partículas y les dijera directamente qué estado tener? ¿Qué cambiaría en el diagrama? ¿Qué problema de diseño crearía eso?


Reto integrador: extender el sistema de partículas

Sección titulada «Reto integrador: extender el sistema de partículas»

Fase 1 — Requisito de entrada: la aplicación extendida

Fase 2 — Evidencias de comprensión

Evidencia 1 — Tu nueva partícula en la Factory

Demuestra con el depurador que tu nuevo tipo de partícula es creado correctamente por la ParticleFactory. Muestra qué rama del condicional se ejecuta y qué valores tiene el objeto Particle* recién creado (tamaño, color, velocidad).

Evidencia 2 — Tu nuevo estado en la _vtable

Demuestra que tu nuevo estado tiene su propia entrada en la _vtable. Captura la _vtable de una partícula en tu nuevo estado y compárala con la de una partícula en NormalState. ¿Qué entradas cambian? ¿Por qué?

Evidencia 3 — La cadena Observer → State completa

Demuestra la cadena completa cuando se activa tu nuevo estado: desde keyPressednotifyonNotifysetState. Muestra con el depurador que el nuevo evento llega a onNotify y que el puntero state cambia a tu nuevo tipo de estado.

Evidencia 4 — Decisión de diseño justificada

Elige UNA decisión de diseño que hayas tomado al extender el código (por ejemplo: por qué tu nuevo estado hereda directamente de State y no de un estado existente; o por qué configuraste la partícula de cierta manera en la factory). Demuestra con el depurador la consecuencia concreta en memoria o comportamiento de esa decisión, y justifica por qué fue la mejor opción.


Reflect: Consolidación y metacognición 🤔

Sección titulada «Reflect: Consolidación y metacognición 🤔»

Usando excalidraw, construye los siguientes gráficos:

  1. Los tres patrones colaborando: dibuja el sistema completo con tus extensiones. Muestra Observer, Factory y State como capas de responsabilidad separadas y las flechas de comunicación entre ellas. ¿Qué responsabilidad tiene cada patrón? ¿Dónde comienza y termina cada uno?

  2. State y la _vtable — conexión con la unidad anterior: en la unidad 5 estudiaste cómo la _vtable implementa el polimorfismo. Ahora conecta eso con el patrón State. Cuando una partícula llama a setState, ¿Cambia su _vtable? ¿Por qué sí o por qué no? Dibuja la respuesta.

  3. ¿Por qué estos patrones?: para cada uno de los tres patrones, completa la frase:

    “Usé el patrón [X] porque sin él tendría que [problema concreto] y eso causaría [consecuencia de diseño].”

Reflexiona también sobre estas preguntas (no hay respuestas incorrectas):

  • ¿Qué decisión de diseño de esta unidad cambiarías ahora que la entiendes mejor?
  • ¿En qué otro contexto, un juego, una app, un sistema que conozcas, usarías alguno de estos tres patrones?
  • ¿Qué pregunta de la sustentación te costó más responder, y por qué crees que fue así?