15 de mayo de 2007

Patrón Modelo Vista Controlador - ActionScript 3.0

Trataré de explicar con lo poco que se, este patrón de diseño utilizando actionscript 3.0 para los amantes de este lenguaje como yo, me servirá a mi para aprender este mundo maravilloso de los patrones y también a todos los lectores que les interese el tema, así que comencemos. ;-)

Las aplicaciones consisten de interfaces de usuario, lógica del negocio y modelos de datos, por ejemplo un componente “combobox” cuenta con elementos de la interfaz del usuario como áreas con scroll ó áreas clickeables, lógica que responde de acuerdo a los eventos generados por el usuario (el usuario de click en una opción del combobox) y modelos de datos (la información que reside en nuestro combobox).

Comúnmente los programadores suelen combinar los 3 elementos en un solo objeto en lugar de crear diferentes objetos para cada responsabilidad, cuando la interfaz y los datos están unidos en un objeto genera los siguientes problemas:

  • Es difícil utilizar los datos de nuestro modelo por fuera de nuestro objeto, por ejemplo si tenemos un inputText y la información escrita en el tiene que ser enviada al servidor, esa información tendría que ser guardada en el mismo objeto y tendríamos que asignarle la responsabilidad de enviar ese dato al servidor, esto viola con los principios de Alta Cohesión.
  • No puedes cambiar la interfaz de usuario fácilmente utilizando el mismo modelo de datos, esto es porque el modelo de datos y la interfaz residen en un solo objeto (están fuertemente acoplados, viola el principio de Bajo Acoplamiento), el cambio de interfaz requeriría copiar todos los datos de la interfaz anterior a la nueva.
  • Múltiples vistas simultaneas de los datos es muy difícil, por ejemplo, tu podrías querer desplegar 2 o más gráficas de los mismo datos y también actualizarlas al cambiar los datos, si tienes 2 gráficas donde los datos y la presentación están mezclados tendrías que actualizar los datos que residen en cada uno de los objetos.


Ahora pasemos a explicar cada una de las partes que conforman el patrón:

El modelo

Es el elemento que almacena los datos, el modelo puede ser tan simple que solamente guarde un solo dato primitivo como un entero así como estructuras complejas de datos por ejemplo otros objetos. Veamos al modelo como un almacén de datos y una cosa muy importante el modelo debe ser “independiente” tanto de la vista y el controlador, esto quiere decir que el modelo no debe saber de la existencia del controlador y la vista, este último punto grábatelo como si fuera algo de vida o muerte xD, esto es debido a que se genera un bajo acoplamiento y te da la posibilidad de cambiar las vistas y controladores a tu antojo sin mover nada en nuestro modelo.

La vista

Es todo lo visual, todo lo que ve el usuario, la vista utiliza los datos del modelo para dibujarse así mismo. La vista puede consistir de elementos como botones, imágenes, formularios, etc.

Como conclusión la vista solamente consiste de elementos visuales y la lógica necesaria para leer los datos del modelo para ser utilizados en la interfaz, como regla la vista no puede actualizar los datos del modelo.

El controlador

El controlador es responsable de capturar los eventos generados por el usuario, y de esa manera actualizar el modelo y la vista. Por ejemplo si el modelo necesita actualizar los datos, el controlador es responsable de esa acción.

Ahora si pasemos a codificar un ejemplo muy sencillo para que se entienda bien el concepto.

Vamos a hacer una clase que va a ser nuestro modelo llamada DataModel, solo va a contener un miembro privado entero que ira incrementándose cada segundo y este se lo notificará a sus listeners. Para que la clase pueda disparar eventos necesitamos heredar de la clase “flash.events.EventDispatcher”, y cuando cambie el estado de nuestro objeto que es cuando se cambia la propiedad number llamamos al método”dispatchEvent” y le pasamos como argumento un objeto del tipo Event, tu tienes la libertad de crear tus propios eventos heredando de la clase Event, la propiedad privada thread es como un hilo de java, al llamar al método start, se llamará al método onTimer cada segundo.

1   package com.jahepi.domain.models {
2
3 import flash.events.EventDispatcher;
4 import flash.events.Event;
5 import flash.utils.Timer;
6 import flash.events.TimerEvent;
7
8 public class DataModel extends EventDispatcher {
9
10 private var _number:Number = 0;
11 private var _thread:Timer;
12
13 public function DataModel() {
14 _thread = new Timer(1000);
15 _thread.addEventListener(TimerEvent.TIMER, onTimer);
16 _thread.start();
17 }
18
19 public function set number(number:Number):void {
20 _number = number;
21 dispatchEvent(new Event(Event.CHANGE));
22 }
23
24 public function get number():Number {
25 return _number;
26 }
27
28 private function onTimer(event:TimerEvent):void {
29 number++;
30 }
31
32 }
33 }


Ahora pasemos a hacer 2 vistas (DataView1 y DataView2) estas heredan de la clase Sprite, y a cada una se la pasa la referencia del Modelo en su constructor, si te fijas en la línea de “_model.addEventListener(Event.CHANGE, onUpdate);”, registramos el listener para que nos avise cuando cambia el estado de nuestro modelo, y este llama al método “onUpdate”, para poder así hacer los cambios pertinentes en nuestra interfaz, cada vista es muy simple pero muestra a grandes rasgos como el modelo notifica a cada una de las vistas cuando su estado cambia y se ve reflejado en cada una de ellas al cambiar la propiedad number del modelo.

DataView1


1   package com.jahepi.domain.views {
2
3
4 import flash.display.Sprite;
5 import flash.text.TextField;
6 import flash.events.Event;
7 import com.jahepi.domain.models.DataModel;
8 import flash.display.Shape;
9
10 public class DataView1 extends Sprite {
11
12 private var _model:DataModel;
13 private var _textField:TextField;
14
15 public function DataView1(model:DataModel) {
16 _model = model;
17 _model.addEventListener(Event.CHANGE, onUpdate);
18
19 var shape:Shape = new Shape();
20 shape.graphics.beginFill(0xff0000, 1);
21 shape.graphics.drawRect(0, 0, 35, 35);
22 shape.graphics.endFill();
23 var textField:TextField = new TextField();
24 textField.text = "vista 1";
25 textField.y = 16;
26
27 _textField = new TextField();
28
29 addChild(shape);
30 addChild(_textField);
31 addChild(textField);
32 }
33
34
35 private function onUpdate(event:Event):void {
36 _textField.text = String(_model.number);
37 }
38
39 }
40 }


DataView2

1   package com.jahepi.domain.views {
2
3 import flash.display.Sprite;
4 import flash.text.TextField;
5 import flash.events.Event;
6 import com.jahepi.domain.models.DataModel;
7 import flash.display.Shape;
8
9 public class DataView2 extends Sprite {
10
11 private var _model:DataModel;
12 private var _textField:TextField;
13
14 public function DataView2(model:DataModel) {
15 _model = model;
16 _model.addEventListener(Event.CHANGE, onUpdate);
17
18 var shape:Shape = new Shape();
19 shape.graphics.beginFill(0x00ff00, 1);
20 shape.graphics.drawCircle(15, 20, 25);
21 shape.graphics.endFill();
22 var textField:TextField = new TextField();
23 textField.text = "vista 2";
24 textField.y = 16;
25
26 _textField = new TextField();
27 _textField.textColor = 0x0000ff;
28
29 addChild(shape);
30 addChild(_textField);
31 addChild(textField);
32 }
33
34
35 private function onUpdate(event:Event):void {
36 _textField.text = String(_model.number);
37 }
38 }
39 }

Ahora le toca el turno de nuestro controlador, este tendrá el objetivo de cambiar las vistas cuando sea apretado un botón en la interfaz, el controlador cuenta con un array que registra todas las referencias de las vistas y también cuenta con la referencia del modelo en su propiedad model por si queremos mas adelante modificar el estado del modelo desde ahí.

1   package com.jahepi.domain.controllers {
2 import com.jahepi.domain.models.DataModel;
3 import flash.display.SimpleButton;
4 import flash.display.Shape;
5 import flash.events.MouseEvent;
6 import flash.display.Sprite;
7
8 public class DataController extends Sprite {
9
10 private var _views:Array;
11 private var _model:DataModel;
12 private var _index:Number = 0;
13
14 public function DataController(model:DataModel) {
15 _views = new Array();
16 _model = model;
17
18 var shape:Shape = new Shape();
19 shape.graphics.beginFill(0xffff00, 1);
20 shape.graphics.drawRect(0,0,30,30);
21 shape.graphics.endFill();
22 var button:SimpleButton = new SimpleButton(shape, shape, shape, shape);
23 button.addEventListener(MouseEvent.CLICK, changeView);
24 button.y = 100;
25 addChild(button);
26 }
27
28 public function addView(view:Sprite):void {
29 _views.push(view);
30 }
31
32 private function changeView(event:MouseEvent):void {
33 if((_index-1) != -1) {
34 removeChild(_views[_index-1]);
35 }
36 if(_index >= _views.length) {
37 _index = 0;
38 }
39 addChild(_views[_index++]);
40 }
41 }
42 }

Y ahora la aplicación final, el punto de entrada donde se crean las instancias:

1   package {
2
3 import flash.display.Sprite;
4 import com.jahepi.domain.views.*;
5 import com.jahepi.domain.models.DataModel;
6 import com.jahepi.domain.controllers.DataController;
7
8 public class MVC extends Sprite {
9
10 public function MVC() {
11
12 var model:DataModel = new DataModel();
13 var controller:DataController = new DataController(model);
14 var view1:DataView1 = new DataView1(model);
15 var view2:DataView2 = new DataView2(model);
16 controller.addView(view1);
17 controller.addView(view2);
18 addChild(controller);
19
20 }
21 }
22 }
23

Unan todo en un proyecto de AS3.0, y verán un pequeño cuadro que es el botón, cuando le dan click se genera la primera vista y cada vez que se cambia el estado del modelo se le notifica a la vista actual cambiado el valor numérico que despliega y si le vuelves a dar click al botón cambia a la segunda vista conservando la secuencia númerica obtenida del modelo. Eso es todo, ojalá no queden dudas ó si me equivoqué en algo no duden en comentar mis horrores.

Un saludote !