Llevo un tiempo pensando en escribir sobre las arquitecturas frontend. Estaba en aquello hasta que recordé que hace ya algunos años atrás, cuando empezaba esto de desacoplar todo el código del cliente, las herramientas se hacían publicidad como el framework X de MVC en el cliente. Comentando este hecho, he visto que hay poca comprensión acerca de la implementación del patrón. Así que eso va esta publicación.

Inicialmente pensé en algo un tanto más elaborado. Así que gracias al amigo Jefferson, el ejemplo que veremos aquí será usado para un próximo artículo en el que será refactorizado.

El patrón

El patrón Model-View-Controller nació en la época Smalltalk. Buscaba desacoplar la interfaz gráfica de una aplicación existente. Esto fue tan importante que se expandió a otros lenguajes orientados a objectos.

El modelo

En MVC, el modelo es el código que realiza alguna acción. Su construcción no conlleva a pensar en cómo ha de lucir cuando la información sea presentada al usuario. Es una interfaz puramente funcional, lo que quiere decir que tiene métodos públicos que pueden ser usado para realizar acciones u obtener información acerca del estado del modelo, así como métodos que pueden cambiar su estado.

No obstante, el modelo debe permitir registrar las vistas que serán notificadas acerca de la actividad del modelo al cambiar de estado.

Varias vistas

Un modelo puede tener múltiples vistas. Una vista provee una interfaz gráfica de usuario (GUI) que obtiene los valores del modelo para mostrarlos mediante la obtención de los datos del mismo.

Cuando un usuario manipula la vista de un modelo, la vista informa al controlador del cambio deseado.

Varios controladores

Las vistas se asocian con los controladores par actualizar al modelo cuando el usuario interactúa con la vista asociada. El controlador puede invocar los métodos del modelo para cambiar su estado. Además, el modelo notificará a toda las vistas registradas que este ha cambiado y que ellas deben actualizar los valores que se muestran al usuario.

Cómo funciona

Empezamos con un modelo capaz de comunicar sus cambios de estado. Por ejemplo, un termómetro,

class TemperatureModel : public QObject...
public:
  double temperature();
  void temperature(double temperature);
signals:
  void changed(double temperature);
...

(Esto es comúnmente llamado Observer Pattern.) Establecemos métodos para obtener la información del estado actual y actualizarlo. Cada vez que se actualiza el estado, se notifica a cada vista registrada emitiendo el mensaje changed().

Así, podemos crear más de una vista que implementen un método que actualicen apropiadamente en los valores en función de los cambios en el modelo y en la propia vista.

La razón por la que se hereda de QObject es porque este nos provee de la infraestructura de notificación y registro de observadores. De esta manera, podemos concentrarnos en la funcionalidad de nuestra aplicación.

Veamos un ejemplo

Ahora veamos cómo trabajan tres vistas distintas de nuestro modelo de temperatura.

 mvc

Cuando iniciamos el programa MVC, creamos y configuramos a los protagonistas de nuestra historia:

TemperatureModel model;

TemperatureView temperatureView;
SliderView sliderView;
DrawingView drawingView;

TemperatureController temperatureController(model, temperatureView);
SliderController sliderController(model, sliderView);
DrawingController drawingController(model, drawingView);

temperatureView.show();
drawingView.show();
sliderView.show();

model.temperature(0);
...

Hay un sólo modelo que mantiene las funcionalidades de nuestra aplicación. Mediante tres controladores configuramos las GUIs de temperaturas para mostrar un formulario, un control horizontal deslizante y un dibujo de un termómetro. Finalmente, inicializamos el modelo.

Las vistas tienen la responsabilidad de presentar la información del modelo. Por ejemplo, la primera vista construye un formulario para ello.

QLabel *label = new QLabel("Temperature");
layout->addWidget(label);

QLineEdit *lineEdit = new QLineEdit();
layout->addWidget(lineEdit);

QPushButton *lowerButton = new QPushButton("-");
QPushButton *raiseButton = new QPushButton("+");

QHBoxLayout *buttonsLayout = new QHBoxLayout();
buttonsLayout->addWidget(raiseButton);
buttonsLayout->addWidget(lowerButton);

layout->addLayout(buttonsLayout);
setGeometry(90, 140, 188, 95);

La segunda es la configuración de un Slider.

setMaximum(100);
setMinimum(0);
setGeometry(90, 80, 270, 30);

Y la última es un canvas con un gráfico que se asemeja a un termómetro. 🙂

p.setBrush(QBrush(Qt::yellow));
p.drawEllipse(10, 100, 50, 50);
p.drawLine(20, 105, 20, 0);
p.drawLine(50, 105, 50, 0);
p.setPen(Qt::PenStyle(Qt::NoPen));
p.drawRect(21, 100 - _temperature, 29, 6 + _temperature);

Por otro lado, los controladores sólo tienen la función de conectar los eventos entre las vistas y el modelo, así como manejar la interacción en el intercambio de datos.

connect(&view, &View::changed, this, &Controller::update);
connect(&view, &View::lower, this, &Controller::down);
connect(&view, &View::raise, this, &Controller::up);

connect(&model, &Model::changed, this, &Controller::change);
connect(this, &Controller::changed, &view, &View::change);

Resumen

Hemos visto cómo interactúan las vistas y sus controladores con el modelo. Este mismo principio ha sido fuente de mucho debate en las arquitecturas del lado cliente. Por eso consideré importante reforzarlo antes de iniciar con otras dos publicaciones: uno sobre cómo refactorizar este ejemplo y la otra sobre las arquitecturas front-end. Dejo el código completo para ser compilado en un repositorio. Saludos a todos. 😉

Anuncios