José Alf. Suárez Moreno’s virtual business card by http://dooid.com: visit

José Alf. Suárez Moreno’s virtual business card by http://dooid.com: visit.

Programación orientada a objeto avanzada con xHarbour (X)

8.4. El controlador.

Comentamos que el controlador principal puede (y debe: por comodidad, por claridad, por las posibilidades de tener controladores independientes y poder reutilizar código) de dividirse en controlador principal y controladores secundarios:

8.4.1. El controlador principal

  • Es el encargado de controlar el flujo de la aplicación.
  • Realizará las llamadas necesarias a los controladores secundarios.
Detalle del método que llama a conceptos

Detalle del método que llama a conceptos

Podemos ver en la imagen el método conceptoRun, llamado desde la opción correspondiente de la vista Menú. Aquí propongo una forma de cargar el controlador correspondiente, almacenando el objeto en la variable ctrConcepto con el objetivo de comprobar en la siguiente llamada si ya ha sido instanciada la clase correspondiente al controlador secundario (ConceptoCtr). En este momento:

  • Asignamos el controlador principal a la variable correspondiente del controlador secundario.
  • Ejecutamos el método showBrowse() del controlador secundario para que nos muestre el formulario que tiene un barra de menú con las opciones del ABM y el browse.
8.4.1. Cada controlador secundario
  • Comunican la capa del modelo de datos con la vista.
  • No deben contener nunca sentencias SQL o de cualquier otro motor de datos, siendo este cometido responsabilidad UNICA de la capa de persistencia.
  • No deben contener nunca código correspondiente a la vista (comportamiento de las columnas de un browse, por ejemplo).
  • Pueden hacer llamadas a otros controladores secundarios.
Siguiendo con la ejecución, desde el controlador principal hemos ejecutado el método que mostrará el browse de conceptos, veamos el código:
Llamada el browse desde el controlador secundario

Llamada el browse desde el controlador secundario

En la imagen podemos ver, además, los métodos new() (contructor de la clase) y setCtrPrincipal() (asignación del controlador principal a este controlador secundario).  Veamos que hace el método showBrowse(),

  • Comprobamos si ya se ha instanciado el browse ( se guardará en la variable brw).
  • En caso de haber sido instanciado, lo mostramos.
  • Si no, cargamos, desde la persistencia, la lista de conceptos (que es un objeto de la clase ArrayList que contiene referencias a objetos de la clase Concepto) y la guardamos en la variable listaConceptos.
  • Instanciamos la vista correspondiente y almacenamos el objeto en la variable brw.
  • Ponemos titulo al browse.
  • Asignamos este controlador a la vista (guadandolo en una variable mediante un método set).
  • Mediante el método toArray() de listaConceptos, asignamos los valores al browse (usamos un arraybrowse de Xailer). Veamos el código del método toArray() de la clase Concepto:
Detalle del método toArray de la clase Concepto

Detalle del método toArray de la clase Concepto

  • toArray devuelve un array que incluye referencias a los métodos set que devuelven valores de los datos del objeto y una referencia a sí mismo. Veremos para que sirve la referencia al propio objeto cuando estudiemos el código del método Edit() para modificar los datos.

Observando el código de la vista ConceptoBrw() podemos ver que las llamadas de los botones de la ToolBar llaman a métodos de este controlador.

En el próximo artículo veremos como funcionan los métodos del ABM.

Programacion orientada a objeto avanzada con xHarbour (IX)

8.- Analizando el código fuente del ejemplo_1

8.1 Propiedades del proyecto en Xailer:

Antes de empezar deberemos indicar a Xailer que vamos a usar la librería complements.lib y el motor de base de datos de SQLite:

Propiedades del proyecto

Propiedades del proyecto

otra de las cosas que haremos será configurar el prg que se ejecutará nada mas arrancar el programa, en nuestro caso, es el prg principal de la aplicación:

Propiedades del proyecto, modulo de inicio.

Propiedades del proyecto, modulo de inicio.

8.2. La entrada al programa:

Este es el código de entrada a la aplicación y la forma de cargar el controlador principal. Hay que observar que cambiamos la forma de arrancar de Xailer:

Código de entrada a la aplicacion

Código de entrada a la aplicación

Desviamos la llamada a través de la función Empezar() que será la que realice la carga del controlador principal.

8.3. Método new() del controlador principal:

Desde el método new() del controlador principal de nuestra aplicación realizaremos la ejecución del primer formulario, en este caso MenuFrm(). Vemos el código:

Método new del controlador principal

Método new del controlador principal

observamos las siguientes instrucciones:

  • en la variable menuPrincipal de la clase Controlador guardamos la referencia del objeto creado de MenuFrm().
  • asignamos el controlador al objeto menuPrincipal. Todas las clases que forman parte de la vista guardaran referencia a su controlador.
  • mostramos el formulario.
Asignación del controlador en la clase.

Asignación del controlador en la clase.

en la imagen anterior podemos ver el código usado para guardar y asignar valores del controlador en la clases pertenecientes a la vista.

Ya hemos cargado el formulario principal de la aplicación desde el que ejecutaremos distintas opciones. Todas estas opciones ejecutarán métodos del controlador principal (ver controlador.prg).

Detalle de la ejecución de opciones

Detalle de la ejecución de opciones

En la imagen anterior podemos ver que los métodos llamados desde las opciones del menú ejecutan métodos del controlador principal.

Seguiremos viendo como funciona el controlador en el próximo artículo.

Programacion orientada a objeto avanzada con xHarbour (VIII)

Siguiendo con la programación por capas MVC y después de ver como se hace la capa de peristencia y como se conecta el modelo de datos a ella, pasamos a estudiar la estructura de la programación usando MVC. Para ello he construido un ejemplo en Xailer (ejemplo_1) que se puede descargar desde el enlace siguiente:

Descargar proyecto del ejemplo 1 hecho en Xailer.

7.- Estructura típica del MVC

Antes de desacargar el código fuente del ejemplo y poneros a leerlo, cayendo en la locura de no entender nada y pensar que todo es un tremendo enredo que va y viene mil veces al mismo sitio, estudiemos este diagrama de como funciona una aplicación típica MVC:

Esquema de un MVC típico.

Esquema de un MVC típico.

Al Inicio del Programa se carga el Controlador Principal que es el encargado de mostrar el Formulario Principal de la aplicación (en este caso menú). Cada vez que pulsemos una opción del menú se ejecutará una acción que llamará al método correspondiente del Controlador Principal; desde donde cargaremos el Controlador Específico de la opción seleccionada. En mi caso siempre muestro un browse (unas veces contendrá toda las filas de la tabla, otras sólo las que me interese), así que al mismo tiempo que cargo el Controlador Específico, muestro el formulario que contiene el browse y los botones para realizar el ABM. Cada opción que pulsemos ejecutará el método correspondiente de su controlador, ejecutando, en su caso, el formulario de edición, el formulario de parámetros para un informe, la llamada a otro controlador ( controlador de un informe, controlador de una pantalla de búsquedas de datos de otro objeto, etc.). Dentro del controlador tendremos los métodos de comporbación de errores del formulario de edición.

Ni que decir tiene que el esquema es general, cada Controlador Específico tendrá los procesos que requiera, no siendo obligatorio que todos tengan la mismas funcionalidades.

Como podemos observar en el esquema, el control del programa siempre está dentro de un controlador, los formularios son “ventanas tontas” que sólo muestran cosas sin participar en la toma real de decisiones del programa. Es importante tener en cuenta también que el modelo de datos tampoco contiene la lógica del control de la aplicación: estará todo en los conroladores.

Estoy seguro (a mi me paso cuando comencé con este sistema de programación) que pensaréis que es una gran pérdida de tiempo y de recursos el crear tantas capas y tan “independientes” una de la otra y que el mantenimiento de los programas se va a convertir en un galimatías para poder seguir el flujo de ejecución; pero no es así, en cuanto nos acostumbremos a pensar en capas, a tener claro que cada una funciona independiente de la otra, veremos que el mantenimiento se hace muy cómodo. Si sabemos que la parte del modelo funciona correctamente y que el formulario no tiene más logica que dibujarse, sólo nos queda preocuparnos de que la lógica de nuestro controlador esté bien.

Otra de las excusas habituales (que yo también he usado cuando empecé con esto) es la de la cantidad de código que tenemos que escribir . . . . bueno . . .  ¿somos programadores o usuarios de IDE? Que sí, que buscamos entornos productivos que nos permitan hacer aplicaciones como churros, pero . . . ¿de cuantas de esas aplicaciones puedes reutilizar su código? ¿Que pasa si un día queremos cambiar el aspecto de nuestros formularios? ¿Y si queremos cambiar de DBF a SQLite o a SQL cliente/servidor? ¡¡¡¡AAAAHHHH!!! Es que eso requiere rehacer toda nuestra aplicación y eso, eso …. nos obliga a ser poco producivos y versátiles.

En el próximo artículo estudiaremos poco a poco el código fuente que he dejado aquí, está hecho para Xailer, pero sólo tendremos que cambiar los formularios y el #include para poder usar practicamente todo con otros entornos xBase; no olvidar que habrá que retocar PersistenciaConexión para adaptarlos a los datasources y dataset de otro entorno). De momento dejo que lo miréis. (Para compilar necesitaréis la librería complements.lib e indicar en las propiedades de proyecto Xailer que quereis usar SQLite).

Programacion orientada a objeto avanzada con xHarbour (VII)

La clase PersistenciaConexion

Esta clase es la encargada de conectar con el motor de datos correspondiente. En este caso conecta con en dataSource para SqlLite de Xailer. Como todo, puede ser mejorada y seguramente reducida para un funcionamiento más optimizado.

CLASS PersistenciaConexion

PROTECTED:
DATA conexion
DATA passwd
DATA dataSource

EXPORTED:
METHOD conectar()
METHOD desconectar()
METHOD getDataSource()
METHOD creaDataSet( cConsulta )
METHOD execute()

END CLASS

METHOD conectar() CLASS PersistenciaConexion

::conexion  := e”.\\Data\\chequelibro.db”
::passwd    := “”

WITH OBJECT ::dataSource := TSQLiteDataSource():New()
:cConnect      := ::conexion
:cPassword     := ::passwd
:lDateAsString := .T.
:lConnected    := .T.
:Create()
END WITH

RETURN self

METHOD desconectar() CLASS PersistenciaConexion
::dataSource:End()
::datasource := nil
RETURN nil

METHOD getDataSource() CLASS PersistenciaConexion
RETURN ::dataSource

METHOD creaDataSet( consulta ) CLASS PersistenciaConexion

local dS

IF Empty( ::dataSource )
::conectar()
END IF

WITH OBJECT dS := TSqlQuery():New()
: oDataSource  := ::dataSource
:cProcess     := “GENERAL”
:nCursorType  := adOpenDynamic
:nLockType    := adLockOptimistic
:cSelect      := consulta
:lOpen        := .T.
:create()
END IF

RETURN dS

METHOD execute( command )

IF Empty( ::dataSource )
::conectar()
END IF

::getDataSource:Execute( command )

::desconectar()

RETURN self

Programacion orientada a objeto avanzada con xHarbour (VI)

Antes de seguir con el tema quiero agradecer a Manu Expósito su colaboración en este proyecto, ya se que ofreció a revisar, optimizar y completar las clases ArrayList, ListIterator, Navigator y Object.

Comentar también que se ha añadido una clase padre Object de la que heredarán todas las clases que usemos en nuestros proyectos.

Descargar nueva versión de la libreria complements.lib.
Descargar documentación de la libreria complements.lib.

6.- Programación por capas:

Como ya se adelantó en el artículo anterior, vamos a aprender a programar usando la tecnología Modelo-Vista-Controlador, que es una programación por capas en la que separamos la vista (o los formularios), el modelo (las clases que contienen los datos) y el controlador (lo que comunicará el modelo con la vista).

Modelo Vista Controlador con Persistencia

Modelo Vista Controlador con Persistencia

En el diagrama podemos ver como funciona la comunicación entre las capas del MVC. Es importante respetar esta comunicación, la vista no puede ni debe comunicar directamente con el modelo en nigún caso, siempre se comunicará a través del controlador.

Si nos fijamos bien en la parte del modelo vemos que este se compone de dos partes. Esto no tiene por que ser necesariamente así, aunque sí muy recomendable para dar claridad al código y poder programar aplicaciones que usen más de un motor de datos distintos fácilmente.

Seguiremos unas sencillas reglas para programar usando MVC+Persistencia:

  • La aplicación arrancará desde un controlador (podemos tener un controlador principal y tantos controladores secundarios como sean necesarios).
  • Un controlador llamará a otro controlador.
  • Desde un controlador llamaremos a los formularios y lo ejecutaremos.
  • Desde un controlador llamaremos al modelo.
  • Tentremos tantas clases en el modelo como sean necesarias. Las clases del modelo que no pertenezcan a la capa de persistencia no contendrán código alguno que sea de acceso a datos.
  • Tendremos una única capa de persistencia que comunicará las clases del modelo con los datos. La capa de persistencia estará compuesta por tantos archivos como sean necesarios (no tiene que estar todo en un solo prg, es más, se aconseja dividirla en tantos archivos como objetos del modelo existan).

Siguiendo estas instrucciones, veamos el diseño de un mantenimiento simple de conceptos (id, descripción).

Empezaremos con la clase Concepto: (perdonad que no este identada, pero este editor del wordpress es penoso del todo).

CLASS Concepto FROM Object

PROTECTED:

DATA id INIT 0
DATA descripción INIT “”

EXPORTED:

METHOD new() CONSTRUCTOR
METHOD setId( id )
METHOD getId()
METHOD setDescripcion( descripcion )
METHOD getDescripcion()
METHOD grabar()
METHOD borrar()
METHOD toString()
METHOD toArray()

ENDCLASS

METHOD new() CLASS Concepto
RETURN self

METHOD setId( id ) CLASS Concepto
::id := id
RETURN nil

METHOD getId() CLASS Concepto
RETURN ::id

METHOD setDescripcion( descripcion ) CLASS Concepto
::descripcion := descripcion
RETURN nil

METHOD getDescripcion() CLASS Concepto
RETURN ::descripcion

METHOD grabar() CLASS Concepto
PersistenciaConcepto():grabar( self )
RETURN nil

METHOD borrar() CLASS Concepto
PersistenciaConcepto():borrar( self )
RETURN nil

METHOD toArray() CLASS Concepto
RETURN { ::getId(), ::getDescripcion(), self )

METHOD toString() CLASS Concepto
RETURN “”

Ahora definieremos la clase de la persistencia:

CLASS PersistenciaConcepto

PROTECTED:

METHOD siguienteId() // este método solo es accesible desde dentro de la clase

EXPORTED:

METHOD lista()
METHOD cargaDatos( id )
METHOD grabar( objeto )
METHOD borrar( objeto )

ENDCLASS

METHOD lista() CLASS PersistenciaConcepto
// Devuelve una lista de todos los conceptos de la tabla.

local lista := ArrayList():New() //lista con los conceptos
local ds //dataset de Xailer para leer los datos
local concepto //objeto concepto

//Usamos el un objeto de la clase PersistenciaConexion para conectar con el
//motor de datos deseado.
//El código está explicado más adelante.

WITH OBJECT PersistenciaConexion():conectar()
// Creo un dataset para traerme los datos desde la base de datos.

dS := :CreateDataSet( “SELECT * FROM concepto” )

WHILE !dS:EoF()
//creamos un objeto de la clase concepto y lo llenamos con
//los datos del dataset

WITH OBJECT concepto := Concepto():new()
:setId( dS:id )
:setDescripcion( dS:Descripción )
END WITH

//añadimos el objeto concepto a la lista
lista:add( concepto )

//siguiente elemento del dataset
dS:Skip()

END DO

//finalizamos el dataset, ya no nos hace más falta
ds:End()

//cierramos la conexion del datasource, tampoco lo necesito ya
:desconectar()

END WITH

//devolvemos la lista de conceptos
RETURN lista

METHOD cargaDatos( id ) CLASS PersistenciaConcepto
// Devuelve el concepto que tenga ese id o un concepto vacio si no lo encuentra.
local ds
local concepto := Concepto():new()
IF !Empty( id )
WITH OBJECT PersistenciaConexion():conectar()
// Dataset para traerme los datos desde la base de datos.
dS := :CreateDataSet( “SELECT * FROM concepto” + ;
“WHERE id = ” + str( id ) )

IF !Empty( dS:id )
//creamos un objeto de la clase concepto y lo llenamos con
//los datos del dataset
WITH concepto := Concepto():new()
:setId( dS:id )
:setDescripcion( dS:Descripción )
END WITH
END IF

//finalizamos el dataset, ya no nos hace más falta
ds:End()
//cierramos la conexion del datasource, tampoco lo necesito ya
:desconectar()
END WITH
END IF

//devolvemos el objeto concepto
RETURN concepto

METHOD siguienteId() CLASS PersistenciaConcepto
// Devuelve el siguiente valor para id.
// Necesario ya que sqlIte no tene autoincrementales
local dS
local id
WITH OBJECT PersistenciaConexion():Conectar()
dS := :creaDataSet( “SELECT max( id ) AS id FROM concepto” )
IF Empty( dS:Id )
id := 1
ELSE
id := dS:Id + 1
END IF
dS:End()
:desconectar()
END WITH
RETURN id

METHOD grabar( objeto ) CLASS PersistenciaConcepto
//graba en la base de datos los datos de un objeto concepto.
local command
IF Empty( objeto:getId() )
//si el id del objeto concepto esta vacio hacemos INSERT
obj:setId( ::siguienteId() )
command := “INSERT INTO concepto (id, descripcion ) VALUES ” + ;
Str( objeto:getId() ) + “, ” + ;
Chr( 34 ) + objeto:getDescripcion() + Chr( 34 ) + ” )”
ELSE
// si tiene valor, hacemos UPDATE
command := “UPDATE concepto SET ” + ;
“decripcion = ” + Chr( 34 ) + objeto:getDescripcion() + Chr( 34 ) + ” ” + ;
“WHERE id = ” + Str( objeto:getId() )
END IF

IF !Empty( command )
PersistenciaConexion():execute( command )
END IF

RETURN nil

METHOD borrar( objeto ) CLASS PersistenciaConcepto
local command
command := “DELETE concepto WHERE id = ” + Str( objeto:getId() )
IF !Empty( command )
PersistenciaConexion():execute( command )
END IF
RETURN nil

Antes de continuar creo que es importante puntualizar la posibilidad que tenemos en xHarbour de ejecutar un método de determinada clase de forma directa sin necesidad de instanciar un objeto de la misma (Por ejemplo, PersistenciaConexion():execute( command ) ).

También, si te has dado cuenta, haciendo uso de la capa de persistencia, cambiar de un motor a otro es tremendamente sencillo; sólo hay que cambiar el código necesario de la capa de persistencia sin tocar para nada una linea de nuestra aplicación.