Model View Controller in Harbour
This article is a translation from ‘Modelo Vista Controlador en Harbour’ written by Manu Expósito. Manu is an spanish database expert who has developed Harbour Data Objects, a class hierarchy to access relational databases from Harbour. You can contact Manu in the email hdoexpoATgmailDOTcom. Any mistake in the traslation is only mine, and I apologize for it.
In this article I’ll try to explain in a practical way how to develop using the MVC design pattern in Harbour.
I would also like it yo be an starting poing, so through your comments and reviews, improve the article itself and the example code.
Let’s start !
Do you know what is a design pattern in programming ?
For many years all the programmers have faced some usual problems and every one solved in a different way. Comparing the solutions in fact there were many points in common. Someone studied this challenges and created a guidelines to solve them wihout using an specific language. Therefore, a design pattern is a defined technique to solve a programming problem. From here I leave you to search more information.
What is the Model View Controller (MVC) design pattern ?
It’s a a way to solve a problem in programming that consists in divide the problem in three layers: the model, the view and the controller. Each layer is specialized in solving its part of the problem. It also can be used with other desing patterns.
This pattern is used mainly in web development, but we can use it also in other kinds of programming.
What is the model ?
You can find a very good explanation of this design patern at Wikipedia — , but I’m going to show it in a practical way.
The model represents the information that our software will manage.
It has to be self-contained, that means that it can’t depend of anything external.
Tiene que ser autosufuciente, quiero decir que no pude depender de nada externo. To obtain the information that define it, it has to access to internal mechanisms that would be use of other patterns, for example use a DAO service to access the information stored in a database. But it also could be retreived fron an XML file, a JSON or a string.
The model doesn’t have to know the existence of the other components, neither the controller nor the view. In fact it should be completely disconnected from them.
As a general rule, classes that represent the model are usually created.
We could create an abstract class and inherit from it the general features to create an specialized class from wich we will instantiate the model objects that we’ll need.
In thos post, I’m going to create a coin converter as a sample.
This is the abstract class that I suggest for the conversion of any coin:
//----------------------------------------------------------------//
#include "HbClass.ch"
//------------------------------------------------------------------
// Class to convert any coin to another
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
//------------------------------------------------------------------
// Conversion from euro to another coin
PROCEDURE convMul( cantidad ) CLASS TConversorModel
::resultado := if( ValType( cantidad ) == "N", cantidad * ::cambio, 0 )
return
//------------------------------------------------------------------
// Conversion from any coin to euro
PROCEDURE convDiv( cantidad ) CLASS TConversorModel
::resultado := if( ValType( cantidad ) == "N", cantidad / ::cambio, 0 )
return
//------------------------------------------------------------------
// Asign the conversion value
METHOD setCambio( cambio ) CLASS TConversorModel
local ret := ::cambio
if ValType( cambio ) == "N"
::cambio := cambio
end if
return ret
//------------------------------------------------------------------
// Get the conversion value
METHOD getCambio() CLASS TConversorModel
return ::cambio
//------------------------------------------------------------------
METHOD getResultado() CLASS TConversorModel
return ::resultado
//------------------------------------------------------------------
And this is is the specialized class to convert euros and pesetas — and old Spanish coin before the euro — and that inherits from the previous class:
//----------------------------------------------------------------//
#include "HbClass.ch"
//------------------------------------------------------------------
// Class to convert pesetas to 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
//------------------------------------------------------------------
// Convert an amount to peseta
METHOD deEurosAPesetas( cantidad ) CLASS TConversorModelEurosPesetas
::convMul( cantidad )
return ::getResultado()
//------------------------------------------------------------------
// Convert an amount to euro
METHOD dePesetasAEuros( cantidad ) CLASS TConversorModelEurosPesetas
::convDiv( cantidad )
return ::getResultado()
//------------------------------------------------------------------
What is the view ?
We said that the model represents the information. This is not worth anything if you can’t see or manage it. That’s what the view is for.
The view can be the way to visualize one or more models in the screen. But it also could be a printed report or a PDF file or anything that show our data.
Here we could use classes or not. For example, in web programming, the view can be HTML pages and usually they are not classes, but they could be classes that include a header and a footer.
In our sample, I’m going to use classes. An abstract one and a specialized one.
The abstract class:
//----------------------------------------------------------------//
#include "HbClass.ch"
//------------------------------------------------------------------
// Abstract class for view
// In the inherit classes we have to implement the VIRTUAL methods
//------------------------------------------------------------------
CREATE CLASS TConversorVista
PROTECTED:
DATA tipoConversion // "Pesetas to Euros" -> 1 "Euros to Pesetas" -> 2
EXPORTED:
METHOD new() CONSTRUCTOR
// This methods will be implemented in every view
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
//------------------------------------------------------------------
// Set the conversion rate
METHOD setTipoConversion( cTipo ) CLASS TConversorVista
local ret := ::tipoConversion
if ValType( cTipo ) == 'C' .and. ( cTipo == '1' .or. cTipo == '2' )
::tipoConversion := cTipo
end if
return ret
//------------------------------------------------------------------
// Get the conversion rate
METHOD getTipoConversion() CLASS TConversorVista
return ::tipoConversion
//------------------------------------------------------------------
And this is the class from which we are going to instantiate our object seen in text format. I suggest you to create a class for your prefered GUI.
//----------------------------------------------------------------//
#include "HbClass.ch"
//------------------------------------------------------------------
// Definition of the VIEW class in text mode
CREATE CLASS TVistaTXT FROM TConversorVista
METHOD msg( cTxt, cTitulo )
METHOD leeOpcion()
METHOD muestraMenu()
METHOD operacionIncorrecta()
// Implementation of the methods of the view interface
METHOD escribeCambio()
METHOD getCantidad()
METHOD acercaDe()
METHOD muestraFin()
END CLASS
//------------------------------------------------------------------
// Show a message in the screen
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 )
//------------------------------------------------------------------
// Show the information in the screen
PROCEDURE escribeCambio( s ) CLASS TVistaTXT
::msg( s, "Resultado" )
return
//------------------------------------------------------------------
// Read the option from the screen
METHOD leeOpcion() CLASS TVistaTXT
local cOpcion := " "
local getList := {}
@ 10, 10 SAY "Elige opcion:" GET cOpcion
READ
return AllTrim( cOpcion )
//------------------------------------------------------------------
// Get the amount to convert
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
//------------------------------------------------------------------
// Show the menu in the screen
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
//------------------------------------------------------------------
// Error message
PROCEDURE operacionIncorrecta() CLASS TVistaTXT
::msg( "Opcion incorrecta..." )
return
//------------------------------------------------------------------
// System information
PROCEDURE acercaDe() CLASS TVistaTXT
::msg( "Ejemplo en modo Texto;del;Patron de Diseño;MVC", "Acerca del ejemplo" )
return
//------------------------------------------------------------------
// Show the end of the execution
PROCEDURE muestraFin() CLASS TVistaTXT
cls
::msg( "Fin de la ejecucion" )
return
//------------------------------------------------------------------
As you can see in the code ot the four classes shown, none of them knows the existence of the others. The model doesn’t know about the view nor the controller, and the view doesn’t know about the model nor the controller.
What is the controller ?
As their name says, is who controls :-)
The controller is responsible for attending events and take care of the request of the user (in some systems it’s called actions that are a special kind of model). For this, it has to know the existence of the views and the models that have to be spread in our system.
It’s an agent between the user, the model and the view. There are different types of controllers. One of the most usual are the front controller that handles all request and take care of them. And ther can be also specialized controllers that only admits the request for with they was created or from a front controller.
Following with our sample, I’m going to create first an abstract class:
//------------------------------------------------------------------
#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
//------------------------------------------------------------------
// Handle the request
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
//------------------------------------------------------------------
// Run at the end
PROCEDURE fin() CLASS TConversorController
// Se haria todo lo del final
break
return
//------------------------------------------------------------------
// Get the view
METHOD getVista() CLASS TConversorController
return ::vista
//------------------------------------------------------------------
// Set the view
PROCEDURE setVista( vista ) CLASS TConversorController
::vista := vista
return
//------------------------------------------------------------------
// Get the model
METHOD getModelo() CLASS TConversorController
return ::modelo
//-----------------------------------------------------------------
// Set the model
PROCEDURE setModelo( modelo ) CLASS TConversorController
::modelo := modelo
return
//------------------------------------------------------------------
And now I’m going to code our specialized controller to convert between pesetas and euros:
//----------------------------------------------------------------//
/*
THE CONTROLLER
Here we receive the user request and route them to the model
*/
#include "HbClass.ch"
//------------------------------------------------------------------
// Controller
CREATE CLASS TConversorEurosPesetasController FROM TConversorController
METHOD gestionDeTipoConversion( cTipo )
END CLASS
//------------------------------------------------------------------
// Conversion control
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
//------------------------------------------------------------------
Note in the code that neither in the model nor in the controller there is any output to the screen, this is done by the specialized class TVistaTXT. It should be nice that any reader will create a TVistaFWH class or a class for any other GUI and post it in the comments of the article. I let the challenge here ;-)
Now we need an starting point that will be our program. If you design it well, it don’t need to be coupled to your GUI:
//------------------------------------------------------------------
#include "hbclass.ch"
//------------------------------------------------------------------
// Main Program
PROCEDURE main()
local oAp := TAplicacion():new()
oAp:ejecuta()
return
//------------------------------------------------------------------
// Main class for our sample
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
//------------------------------------------------------------------
// Starting point
PROCEDURE ejecuta() CLASS TAplicacion
::controlador:getVista():muestraMenu()
::controlador:despachaAcciones()
return
//------------------------------------------------------------------
I hope that with this post we’ll start a discussion that promote this great publication that is Harbour magazine of our fried José Luis. If the article has a big reception, we can do other about design patterns as DAO, Facade, Adapter, Singleton or Decorator… to say some interesting ones.
I’m waiting for your comments…
Regards,
Manu Expósito
PD: All the source code is available at: https://github.com/JoseluisSanchez/MVC_Harbour