Modelo Vista Controlador en Harbour

José Luis Sánchez
Harbour Magazine
Published in
9 min readJun 21, 2018

Este es un artículo de Manu Expósito para Harbour Magazine. Manu es un conocido experto en bases de datos y ha desarrollado Harbour Data Objects, una jerarquía de clases para acceder a BB.DD. relacionales con Harbour. Puedes contactar con él en hdoexpoARROBAgmailPUNTOcom

Con este artículo pretendo explicar de una manera muy práctica como programar usando el patrón de diseño MVC en xBase.

Además me gustaría que fuera el punto de inicio para que mediante las reseñas que hagáis, mejorar el propio articulo y el ejemplo que ilustra la explicación.

¡¡¡Sin más dilación empecemos!!!

¿Sabes qué es un patrón de diseño en programación?

Durante muchos años todos los programadores hemos afrontado unos problema que eran comunes y que cada uno resolvía de una manera diferente. Si se comparaban, realmente había muchas similitudes. Alguien se dedicó a estudiar esos retos y sin codificar en un lenguaje concreto hizo unas directrices por escrito donde se indicaba como solucionar el problema. Por lo tanto un patrón de diseño es una técnica definida para afrontar un problema informático. A partir de aquí dejo a cada uno que indague más.

¿En qué consiste el patrón de diseño Modelo Vista Controlador (MVC)?

Es una manera de resolver un problema en la programación que consiste en dividir el problema en tres capas: el modelo, la vista y el controlador. Cada una de ellas está especializada en la solución de una parte del problema. Además se puede usar con otros patrones de diseño.

Sobre todo se seuele utilizar en el desarrollo para internet pero que podemos aprovechar para otros tipos de programación.

¿Qué es el modelo?

Realmente en Wikipedia están muy bien explicados los componentes de este patrón de diseño, pero voy a intentar mostrar qué es de una manera muy práctica.

El modelo representa la información que va a tratar nuestro programa.

Tiene que ser autosuficiente, quiero decir que no pude depender de nada externo. Para obtener la información que lo conforma tiene que acceder a mecanismos internos que podrían ser el uso de otros patrones, por ejemplo usar un servicio DAO para acceder a la información guardada en una base de datos. Pero también podría ser obtenida desde un XML o un JSon o una cadena etc…

El modelo no tiene porqué conocer la existencia de los otros componentes, ni del controlador ni de la vista.

De hecho debería estar completamente desacoplado del resto.

Como regla general se suelen crear clases que representan el modelo.

Podríamos crear una clase abstracta y de ella heredaremos las características generales para crear una clase especializada de la cual instaciaremos los objetos modelo que se necesiten.

El ejemplo va a consistir en un conversor de monedas.

Esta sería la clase abstracta que propongo para la conversión de cualquier moneda:

//----------------------------------------------------------------//

#include "HbClass.ch"

//------------------------------------------------------------------
// Clase para convertir cualquier moneda a otra

CREATE CLASS TConversorModel

PROTECTED:
DATA cambio
DATA resultado

EXPORTED:
METHOD new( valorCambio ) CONSTRUCTOR
METHOD getResultado()
// SET GET
METHOD setCambio( cambio )
METHOD getCambio()

PROTECTED:
METHOD convMul( cantidad )
METHOD convDiv( cantidad )

END CLASS

//------------------------------------------------------------------
// Constructor

METHOD new( valorCambio ) CLASS TConversorModel

if ValType( valorCambio ) == "N"
::cambio := valorCambio
end if

return Self

//------------------------------------------------------------------
// Pasa euros a la moneda

PROCEDURE convMul( cantidad ) CLASS TConversorModel

::resultado := if( ValType( cantidad ) == "N", cantidad * ::cambio, 0 )

return

//------------------------------------------------------------------
// Pasa las monedas a euros

PROCEDURE convDiv( cantidad ) CLASS TConversorModel

::resultado := if( ValType( cantidad ) == "N", cantidad / ::cambio, 0 )

return

//------------------------------------------------------------------
// Asigna el cambio

METHOD setCambio( cambio ) CLASS TConversorModel

local ret := ::cambio

if ValType( cambio ) == "N"
::cambio := cambio
end if

return ret

//------------------------------------------------------------------
// Obtiene el cambio

METHOD getCambio() CLASS TConversorModel
return ::cambio

//------------------------------------------------------------------

METHOD getResultado() CLASS TConversorModel
return ::resultado

//------------------------------------------------------------------

Y esta otra sería la clase especializada para la conversión de euros y pesetas y que hereda de la anterior:

//----------------------------------------------------------------//

#include "HbClass.ch"

//------------------------------------------------------------------
// Clase para convertir pesetas a Euros

CREATE CLASS TConversorModelEurosPesetas FROM TConversorModel

METHOD new() CONSTRUCTOR
METHOD deEurosAPesetas()
METHOD dePesetasAEuros()

END CLASS

//------------------------------------------------------------------
// Constructor

METHOD new() CLASS TConversorModelEurosPesetas

::setCambio( 166.386 )

return Self

//------------------------------------------------------------------
// Convierte un importe a ptas

METHOD deEurosAPesetas( cantidad ) CLASS TConversorModelEurosPesetas

::convMul( cantidad )

return ::getResultado()

//------------------------------------------------------------------
// Convierte un importe a euros

METHOD dePesetasAEuros( cantidad ) CLASS TConversorModelEurosPesetas

::convDiv( cantidad )

return ::getResultado()

//------------------------------------------------------------------

¿Qué es la vista?

Hemos dicho que el modelo representa la información. Eso en sí no vale de nada si no se puede tratar o ver. Para eso está la vista.

La vista puede ser la manera de visualizar uno más modelos por pantalla. Aunque también podría ser un informe que se imprime o un PDF o cualquier mecanismo en el que se representaciónde datos.

Aquí podríamos usar clases o no.

Por ejemplo, en la programación para internet, suelen ser las páginas web y no suelen ser objetos de una clase sino HTML.

Pero también podrían ser clases que contengan por ejemplo una cabecera y un pie.

Aquí van a ser también clases. Una abstracta y otra especializada:

La clase abstracta:

//----------------------------------------------------------------//

#include "HbClass.ch"

//------------------------------------------------------------------
// Clase para Abstracta para la creacion de vistas.
// En las clases derivadas hay que implementar al menos estos metodos VIRTUALES
//------------------------------------------------------------------

CREATE CLASS TConversorVista

PROTECTED:
DATA tipoConversion // "Pesetas a Euros" -> 1 "Euros a Pesetas" -> 2

EXPORTED:
METHOD new() CONSTRUCTOR
// Se implementan en cada vista
METHOD msg( cTxt, cTitulo ) VIRTUAL
METHOD muestraMenu() VIRTUAL
METHOD getCantidad() VIRTUAL
METHOD escribeCambio( s ) VIRTUAL
METHOD acercaDe() VIRTUAL
METHOD muestraFin() VIRTUAL

// SET GET
METHOD setTipoConversion( cTipo )
METHOD getTipoConversion()

END CLASS

//------------------------------------------------------------------
// Constructor

METHOD new() CLASS TConversorVista
return self

//------------------------------------------------------------------
// Asigna el tipo Conversion

METHOD setTipoConversion( cTipo ) CLASS TConversorVista

local ret := ::tipoConversion

if ValType( cTipo ) == 'C' .and. ( cTipo == '1' .or. cTipo == '2' )
::tipoConversion := cTipo
end if

return ret

//----------------------------_-------------------------------------
// Obtiene el tipo Conversion

METHOD getTipoConversion() CLASS TConversorVista
return ::tipoConversion

//------------------------------------------------------------------

Y esta es la clase desde la que vamos a instanciar nuestro objeto vista en formato texto. Como reto, propongo que hagais una clase para un GUI, por ejemplo FWH.

//----------------------------------------------------------------//

#include "HbClass.ch"

//------------------------------------------------------------------
// Definicion de la clase VISTA tipo texto

CREATE CLASS TVistaTXT FROM TConversorVista

METHOD msg( cTxt, cTitulo )
METHOD leeOpcion()
METHOD muestraMenu()
METHOD operacionIncorrecta()
// Implementacion de los metodos de la interfaz vista
METHOD escribeCambio()
METHOD getCantidad()
METHOD acercaDe()
METHOD muestraFin()

END CLASS

//------------------------------------------------------------------
// Saca un mensaje en pantalla

METHOD msg( cMsg, cTitulo ) CLASS TVistaTXT

if ValType( cTitulo ) != 'C'
cTitulo := "Atencion"
endif

cTitulo := ";" + cTitulo + ";;;"

if ValType( cMsg ) != 'C'
cMsg := " "
endif

return Alert( cTitulo + cMsg )

//------------------------------------------------------------------
// Se encarga de sacar por pantalla la informacion

PROCEDURE escribeCambio( s ) CLASS TVistaTXT

::msg( s, "Resultado" )

return

//------------------------------------------------------------------
// Acepta el tipo de opcion por pantalla

METHOD leeOpcion() CLASS TVistaTXT

local cOpcion := " "
local getList := {}

@ 10, 10 SAY "Elige opcion:" GET cOpcion

READ

return AllTrim( cOpcion )

//------------------------------------------------------------------
// Acepta el importe que se va a tratar

METHOD getCantidad() CLASS TVistaTXT

local nCantidad := 0
local getList := {}

@ 15, 10 SAY "Importe:" GET nCantidad

READ

// Limpia pantalla
@ 15, 10 SAY Space( 30 )

return nCantidad

//------------------------------------------------------------------
// Muestra el menu por pantalla

PROCEDURE muestraMenu() CLASS TVistaTXT

cls

? " +----------------------------------------+"
? " | Indica operacion que quieres realizar: |"
? " +----------------------------------------+"
?
? " [1] De pesetas a euros"
? " [2] De euros a pesetas"
? " [3] Acerca de..."
? " [0] Salir"

return

//------------------------------------------------------------------
// Mensaje de error

PROCEDURE operacionIncorrecta() CLASS TVistaTXT

::msg( "Opcion incorrecta..." )

return

//------------------------------------------------------------------
// Informacion del sistema

PROCEDURE acercaDe() CLASS TVistaTXT

::msg( "Ejemplo en modo Texto;del;Patron de Diseño;MVC", "Acerca del ejemplo" )

return

//------------------------------------------------------------------
// Aqui ira todo lo que se debe hacer al finalizar la ejecucion a nivel de vista

PROCEDURE muestraFin() CLASS TVistaTXT

cls

::msg( "Fin de la ejecucion" )

return

//------------------------------------------------------------------

Como podeis ver en el código de las cuatro clases vistas hasta ahora ninguna conoce la existencia de las demás. El modelo no sabe nada de la vista ni del controlador y la vista no sabe nada del modelo ni del controlador.

¿Qué es el controlador?

Como su nombre indica es el que controla :-)

El controlador se encarga atender a los eventos y de despachar las peticiones del usuario (en algunos sistemas se llama acciones que son un tipo de modelo especial). Para ello tiene que conocer la existencia de las vistas y de los modelos que se tienen que propagar por nuestro programa.

Realmente es un intermediario entre el usuario, el modelo y la vista.

Puede haber diferentes tipos de controladores. Uno de los más difundidos son:

El controlador frontal que es único en el sentido que admite todas las peticiones y las despacha todas.

Y puede haber controladores especializados que sólo admiten peticiones y eventos para los que está concevido o de un controlador frontal.

Para seguir con el mismo criterio vamos poner primero la clase abstracta:

//------------------------------------------------------------------

#include "HbClass.ch"

//------------------------------------------------------------------
// Control 1/1

CREATE CLASS TConversorController

PROTECTED:
DATA vista
DATA modelo

EXPORTED:
METHOD new( vista, modelo ) CONSTRUCTOR
METHOD gestionDeTipoConversion( cTipo ) VIRTUAL
METHOD despachaAcciones()
METHOD fin()
// SET GET
METHOD getVista()
METHOD setVista( vista )
METHOD getModelo()
METHOD setModelo( modelo )

END CLASS

//------------------------------------------------------------------
// Constructor

METHOD new( vista, modelo ) CLASS TConversorController

::vista := vista
::modelo := modelo

return self

//------------------------------------------------------------------
// Gestiona las peticiones

PROCEDURE despachaAcciones() CLASS TConversorController

local cTipo

while .t.

switch cTipo := ::vista:leeOpcion()

case '0'
::vista:muestraFin()
::fin()
exit

case '1'
case '2'
::vista:setTipoConversion( cTipo )
::gestionDeTipoConversion()
exit

case '3'
::vista:acercaDe()
exit

otherwise
::vista:operacionIncorrecta()

end switch
end

return

//------------------------------------------------------------------
// Se ejecuta al final

PROCEDURE fin() CLASS TConversorController

// Se haria todo lo del final
break

return

//------------------------------------------------------------------
// Obtiene la vista

METHOD getVista() CLASS TConversorController
return ::vista

//------------------------------------------------------------------
// Asigna la vista

PROCEDURE setVista( vista ) CLASS TConversorController

::vista := vista

return

//------------------------------------------------------------------
// Obtiene el modelo

METHOD getModelo() CLASS TConversorController
return ::modelo

//------------------------------------------------------------------
// Asigna el modelo

PROCEDURE setModelo( modelo ) CLASS TConversorController

::modelo := modelo

return

//------------------------------------------------------------------

Y ahora nuestro controlador especializado para la conversión entre pesetas y euros:

//----------------------------------------------------------------//
/*
El CONTROLADOR
Desde aqui se reciben las peticiones del usuario y se trasladan al MODELO
*/

#include "HbClass.ch"

//------------------------------------------------------------------
// Controlador

CREATE CLASS TConversorEurosPesetasController FROM TConversorController

METHOD gestionDeTipoConversion( cTipo )

END CLASS

//------------------------------------------------------------------
// Control de conversiones

PROCEDURE gestionDeTipoConversion() CLASS TConversorEurosPesetasController

local cantidad := ::vista:getCantidad()

switch ::vista:getTipoConversion()

case '1'
::vista:escribeCambio( hb_ntos( cantidad ) + " pesetas son: " + ;
hb_ntos( ::modelo:dePesetasAEuros( cantidad ) ) + " euros" )

exit

case '2'
::vista:escribeCambio( hb_ntos( cantidad ) + " euros son: " + ;
hb_ntos( ::modelo:deEurosAPesetas( cantidad ) ) + " pesetas" )

exit

otherwise
::vista:msg( "---< Se ha producido un ERROR >---" )

end switch

return

//------------------------------------------------------------------

Si observais el código vereis que ni en el modelo ni en el cotrolador hay salidas a pantalla de eso se encarga la clase especializada TVistaTXT, me encantaría que halguien hiciera una clase TVistaFWH o cualquier otra clase para otro IDE. Ahí queda el reto ;-)

Ahora necesitamos un punto de entrada que será nuestro programa.

Si lo diseñais bien tampoco tiene que estar acoplado al GUI que se use:

//------------------------------------------------------------------

#include "hbclass.ch"

//------------------------------------------------------------------
// Programa principal de la prueba

PROCEDURE main()

local oAp := TAplicacion():new()

oAp:ejecuta()

return

//------------------------------------------------------------------
// Clase principal para el ejemplo de pruebas etc

CLASS TAplicacion

DATA controlador

METHOD new() CONSTRUCTOR
METHOD ejecuta()

END CLASS

//------------------------------------------------------------------
// Constructor

METHOD new() CLASS TAplicacion

local oVista := TVistaTXT():new()
local oModelo := TConversorModelEurosPesetas():new()

::controlador := TConversorEurosPesetasController():new( oVista, oModelo )

return self

//------------------------------------------------------------------
// Metodo que pone en marcha el sistema

PROCEDURE ejecuta() CLASS TAplicacion

::controlador:getVista():muestraMenu()
::controlador:despachaAcciones()

return

//------------------------------------------------------------------

Espero que con este pequeño articulo se cree un debate que promocione a esta gran herramienta que es Harbour Magazine de nuestro gran amigo José Luis, a Harbour y al uso de patrones de diseño en nuestros programas. Si es así podríamos hacer otros artículos con otros patrones de diseño como por ejemplo DAO, Facade, Adapter, Singlenton o Decorator… por poner algunos interesantes.

Espero vuestras reseñas…

Saludos.

Manu Expósito

PD: Todo el código del artículo está disponible en: https://github.com/JoseluisSanchez/MVC_Harbour

--

--