Unidad 6
Introducción 📜
Sección titulada «Introducción 📜»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.
Rúbrica de evaluación de la unidad 📝
Sección titulada «Rúbrica de evaluación de la unidad 📝»Requisito de salida (condición necesaria)
Sección titulada «Requisito de salida (condición necesaria)»Rúbrica analítica
Sección titulada «Rúbrica analítica»| 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.
Seek: Investigación 🔎
Sección titulada «Seek: Investigación 🔎»Actividad 01
Sección titulada «Actividad 01»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; }}Actividad 02
Sección titulada «Actividad 02»Análisis arquitectónico con el depurador
Sección titulada «Análisis arquitectónico con el depurador»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?
Actividad 03
Sección titulada «Actividad 03»Mapa conceptual de arquitectura
Sección titulada «Mapa conceptual de arquitectura»🧐🧪✍️ Usando excalidraw, dibuja un
diagrama de flujo de mensajes que muestre la cadena completa:
keyPressed → notify → onNotify → setState → nueva _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?
Apply: Aplicación 🛠
Sección titulada «Apply: Aplicación 🛠»Actividad 04
Sección titulada «Actividad 04»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
keyPressed → notify → onNotify → setState. 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 🤔»Actividad 05
Sección titulada «Actividad 05»Usando excalidraw, construye los siguientes gráficos:
-
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?
-
State y la _vtable — conexión con la unidad anterior: en la unidad 5 estudiaste cómo la
_vtableimplementa el polimorfismo. Ahora conecta eso con el patrón State. Cuando una partícula llama asetState, ¿Cambia su_vtable? ¿Por qué sí o por qué no? Dibuja la respuesta. -
¿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í?