miércoles, 1 de mayo de 2013

El uso de clases y objetos en la programación científica


Esta es una de mis entradas más pretenciosas, quizá el título de esta entrada es un tanto pueril para muchos programadores experimentados.
Mi motivación para la misma es el hecho que pocas veces logra darse la preponderancia suficiente a este asunto en los cursos de programación de las carreras científicas.
Por lo general, se orienta la programación hacia el paradigma de la programación estructurada, paradigma poco elegante si se quiere trabajar en proyectos grandes o si se quiere implementar fácilmente el código fuente escrito, si queremos que otros utilicen nuestro código.

Como científico, es preciso aprovechar todas las herramientas disponibles para resolver nuestros problemas de investigación o de cualquier otra índole académica. Si bien no soy experto en programación, mi experiencia programando desde hace unos 3 años me ha permitido darme cuenta de la importancia que tiene el paradigma de la programación orientada a objetos si se quiere trabajar en el diseño de simulaciones y/o en instrumentación científica. 


Una idea de lo que es una clase


Dejando de lado los formalismos, pienso que una clase se puede definir como un tipo (abstracto) de datos (tipo de variable) más unos métodos propios de la clase (del tipo de datos) a la que pertenecen. Así, cuando se crea una clase, es posible implementarla en nuestros programas, incluir cambios y funciones den la clase y automáticamente los cambios de propagarán a los programas donde la clase se implemente. Para no dejar las cosas en el aire, pensemos en una aplicación al álgebra de vectores como un problema de programación y miremos su solución desde el punto de vista de la programación estructurada y la programación orientada a objetos.

Manipulando vectores con programación estructurada

Mediante la programación estructurada, la salida rápida para le problema se hace a través del uso de arreglos (arrays) lo cual está bien, pero es poco elegante, por ejemplo, para definir el vector i que apunta en la dirección x, puede hacerse algo como esto (código fuente C/C++)

    double i_vector[3];

    i_vector[0]=1;
    i_vector[1]=0;
    i_vector[2]=0;

Lo cual servirá, pero como dije antes, es poco elegante, para tener la base completa {i,j,k} tendríamos el siguiente lío

    double i_vector[3];
    double j_vector[3];
    double k_vector[3];

    i_vector[0]=1;
    i_vector[1]=0;
    i_vector[2]=0;

    j_vector[0]=0;
    j_vector[1]=1;
    j_vector[2]=0;

    k_vector[0]=0;
    k_vector[1]=0;
    k_vector[2]=1;

Ahora, pensemos que queremos definir un producto interno entre vectores (producto punto). Hasta ahora nuestro código solo tiene arreglos definidos, por lo tanto, necesitamos crear una función que lleve a cabo la tarea de calcular el producto punto. Por ejemplo


double productoPunto(double vector1[],double vector2[])
{
    int i=0;
    double res=0;
 
    for(i=0;i<sizeof(vector1);i++)
    {
        res = vector1[i] * vector2[i] + res;
    }
 
    return res;
}


la cual puede llamarse así
 
    punto(i_vector,k_vector)

Funciona, pero siempre queda la sensación que hace falta algo, la sensación que nuestro programa puede ser mejor. Y de hecho, es posible.


Manipulando vectores con programación orientada a objetos


Había mencionado que la programación orientada a objetos hacía uso del concepto de clase a la hora de programar, también había dicho que la clase incluye funciones. Siguiendo con nuestro problema modelo, he escrito una clase llamada cVector (el nombre lo he puesto yo), la clase sirve para manipular vectores en un espacio 3-D. La definición se da a continuación


#include<iostream>
#include<string>
#include <cmath>

class cVector
{
private:

// private quiere decir que las variables a continuación solo pueden accederse desde la clase misma
// es decir, no pueden ser modificadas al momento de implementar el codigo
// por esta razon hay que crear alguna funcion que modifique estos valores
// más adelante veremos las funciones que manipulan estas variables


// Defino una variable para cada coordenada espacial
        double coordenada_x;
        double coordenada_y;
        double coordenada_z;

public:

// public quiere decir que todo lo que se defina a continuacion puede ser accedido al momento de
// implementar la clase

// esta funcion se encarga de modificar las variables privadas de coordenadas

        void colocarCoordenadas(double dX,double dY, double dZ);

// esta funcion retorna la norma del vector
        double normaVector();



// esta funcion permite la igualacion de vectores, es decir, tener por ejemplo vector_a y vector_b
// y hacer que vector_b contenga lo mismo que vector_a solo con hacer
// vector_b=vector_a;
// este proceso se conoce como "sobrecarga de operadores" y consiste en definir las operaciones
// para los operadores de uso común, en este caso, el operador de asignación

        cVector& operator= ( const cVector &rhs );

// esta sobrecarga del operador <, calcula las normas de los vectores y nos dice cual tiene una norma mayor
        bool operator< ( const cVector& other) const;

//con esta sobrecarga implementamos el producto punto entre vectores
        double operator*( const cVector& other ) const;

// con esta sobrecarga se implementa la suma de vectores
        cVector&  operator+(const cVector &other);

};


// Luego de haber declarado la clase y sus funciones, se debe colocar el código de la implementacion de las // funciones, es decir, lo que hacen
void cVector::colocarCoordenadas(double dX,double dY, double dZ)
{
        coordenada_x=dX;
        coordenada_y=dY;
        coordenada_z=dZ;
}


double cVector::normaVector()
{
         return sqrt( coordenada_x*coordenada_x + coordenada_y*coordenada_y + coordenada_z*coordenada_z);
}


cVector& cVector::operator= ( const cVector &rhs )
{

 if ( this == &rhs )    //Verifico si se trata del mismo objeto
    {
      return *this;
    }
  else
    {

        coordenada_x=rhs.coordenada_x;
        coordenada_y=rhs.coordenada_y;
        coordenada_z=rhs.coordenada_z;


        return *this;
    }

}

bool cVector::operator< ( const cVector& other) const
{
    return  ( sqrt( coordenada_x*coordenada_x + coordenada_y*coordenada_y + coordenada_z*coordenada_z) < sqrt( other.coordenada_x*other.coordenada_x + other.coordenada_y*other.coordenada_y + other.coordenada_z*other.coordenada_z) );
}

double cVector::operator*(const cVector &other) const
{
    return coordenada_x*other.coordenada_x + coordenada_y*other.coordenada_y + coordenada_z*other.coordenada_z;
}

cVector&  cVector::operator+(const cVector &other) //const
{
     coordenada_x+=other.coordenada_x;
     coordenada_y+=other.coordenada_y;
     coordenada_z+=other.coordenada_z;
   
     return *this;
}

A la hora de utilizar esta clase, basta con incluirla en los archivos de cabecera y utilizar cada una de sus funciones fácilmente

#include "cVector.hpp"

int main(void)
{
    cVector I_vector;
    cVector J_vector;
    cVector K_vector;

    I_vector.colocarCoordenadas(1,0,0);
    J_vector.colocarCoordenadas(0,1,0);
    K_vector.colocarCoordenadas(0,0,1);

    std::cout << "El producto punto entre I y K es = " <<  I_vector*K_vector  << std::endl;
    std::cout << "La magnitud de la suma vectorial entre I y J " << (I_vector+J_vector).normaVector() << std::endl;
 
//vemos como la suma de vectores se incorpora fácilmente
    if( (I_vector+J_vector)<K_vector)
    {
std::cout << "La magnitud de I + J es menor que la de K " << std::endl;
}
else
{
std::cout << "La magnitud de I + J es mayor que la de K " << std::endl;
}

}

La elaboración de la clase nos permite un control mayor de todas las cosas que pasan con nuestras variables, a la hora de implementar la clase, todo el código detrás de las funciones queda "oculto" pudiéndonos concentrar más en como aprovechar la clase y no en como encajar nuestro problema en la programación estructurada.

La clase cVector y su código de implementación pueden descargarse usando este enlace