Programación Orientada a Objetos (POO)

La programación orientada a objetos es un técnica utilizada por los programadores para adaptar el trabajo de programar a la Psicología y Filosofía humana. Es una forma humana de programar y plasmar en programas de computadora objetos de la vida real, por ejemplo puedo modelar en un programa de computadora un mamífero como un gato, suena algo complicado pero más adelante veremos que no es así.

En la realidad podemos considerar a un objeto como todo aquello que ocupa un espacio, tiene volumen y además podemos percibirlo por nuestros sentidos, ya sea animado o inanimado. Un objeto puede ser una casa, un animal, una silla, etc.

Para hacer un programa orientado a objetos debemos primero identificar que objetos vamos a necesitar para resolver el problema que estemos tratando, para hacer esto nos guiaremos en como un objeto es en la vida real. Una vez que hagamos este análisis estaremos listos crear primero las clases y luego los objetos en nuestro programa.

Ahora entrando en más detalles de las características de la Programación Orientada a Objetos, esta  encapsula datos (atributos) y funciones (comportamientos) en objetos, los datos y funciones están íntimamente relacionados entre sí. Los objetos tienen la propiedad de ocultamiento de información, esto significa que si bien saben como comunicarse entre si a través de interfaces bien definidas, normalmente no se permite que un objeto sepa como están implementados otros objetos.

Por que utilizar Programación Orientada a Objetos

Porque podemos hacer que el trabajo de programar sea más sencillo, nuestros programas será serán más claros y ordenados.

Por ejemplo si quiero almacenar la edad de una persona, tengo que hacerlo en una variable de tipo entero (int), si quiero almacenar su nombre tendré que utilizar una cadena, y si quiero otros datos de la persona tendré que seguir creando variables. Porque no crear una variable de tipo persona directamente que de una sola vez contenga todo lo que quiero saber de una persona, como su nombre, edad, sexo, etc. Esto es posible, precisamente es lo que nos permite hacer la programación orientada a objetos, podemos crear una variable persona que contenga muchos datos de una sola vez, pero no le llamaremos variable sino clase, podemos crear clases en un programa y después las podemos utilizar.

Concepto de clase

Una clase es una agrupación de datos (atributos),generalmente contenidos en variables, y de funciones, (comportamientos) que operan sobre esos datos.

Definición de una clase

Sintaxis

class nombreClase
{
         datos
            funciones
};

nombreClase  Es cualquier nombre que elija para darle a mi clase, menos palabras reservadas del lenguaje.

datos           Son los datos o atributos que tendrá mi clase, como son generalmente variables, se debe definir las variables en esta parte.

funciones      Se debe escribir la definición de las funciones

Una clase se la crea inspirada en un objeto de la vida real, por ejemplo quiero modelar un gato en un programa entonces tengo que escribir una clase que se llame gato. Como sigue:

class gato

{

};

Al anterior ejemplo todavía no contiene ningún atributo ni tampoco ninguna función pero la iremos refinando poco a poco.

También podemos crear clases que en la realidad no se consideren como un objeto por ejemplo una clase hora.

Abstracción

Abstracción significa considerar aisladamente las cualidades de un objeto, consiste en rescatar las características más importantes de algo y representarlo de una manera más simplificada.

En el ejemplo de la construcción de nuestra clase gato se debe de abstraer las cualidades más importantes de un gato y de esta forma podremos representarlo en un programa de computadora.

class gato
{
char color[20]; //atributo
int vidas; //atributo
void miau() //comportamiento
   {
    cout<<”Miiaaauu!!!“;
   }
};

Ahora la clase gato esta más completa porque se han identificado las cualidades más importante de un gato, en este caso hemos dicho que un gato tiene un color y tiene vidas como atributos y además de ello dice miau que es una forma de comportase, esta es un forma muy simplificada de describir a un gato pero nos basta para crear la clase gato.

A los datos dentro de una clase se les llama variables miembro y a las funciones se les llama funciones miembro.

Encapsulación

La encapsulación es una característica de la POO que trata de mantener unidos los datos y los comportamientos de una entidad u objeto.

Una clase encapsula atributos y funciones lo que hace posible el ocultamiento de información, que quiere decir que cuando yo quiera usar una determinada clase, creando objetos de ella, no necesito conocer los detalles de su implementación. Si nuestra clase gato la utilizaría un extraño cuando utiliza la función miau() sólo tiene que saber que con esa función el gato maulla pero no como es que esta hecha la función.

Control de Acceso a los miembros

Entendemos por miembros a todos los atributos y funciones, para controlar el acceso a los miembros de una clase se utilizan tres tipos de controladores: public (público), private (privado) y protected (protegido).

Con estos controladores le damos ciertas restricciones de acceso a los miembros de nuestra clase.

Todos los miembros, tanto atributos como funciones, que sean declarados como privados sólo pueden ser accedidos desde funciones pertenecientes a la clase.

Los miembros que sean declarados públicos pueden ser accedidos desde cualquier parte del programa.

Refinando más nuestra clase gato quedaría de la siguiente manera:

class gato

{

private:

char color[20]; //atributo

int vidas; //atributo

public:

void miau() //comportamiento

   {

    cout<<”Miiaaauu!!!“;

   }

};

Generalmente los datos o atributos son privados porque no queremos que nadie los conozca y no tengan acceso a ellos. Las funciones en cambio son publicas para que puedan ser accedidas y de esta manera manipular la clase.

Acceso a los miembros de una clase

Para tener acceso a los miembros de una clase podemos hacerlo desde dos partes del programa:

-          Desde dentro de la clase, accedemos a cualquier miembro directamente por el nombre, si es una variable por el nombre que le dimos a la variable y si es una función de igual manera por su nombre.

-          Desde fuera de la clase, podría ser desde la función principal o cualquier otra sección del programa que no pertenezca a la clase. Para acceder a un miembro lo hacemos de la siguiente manera

NombreObjeto.miembro

Utilizamos un punto entre el nombre del Objeto, que pertenece a una clase, y el miembro.

Concepto de Objeto

Un objeto es un elemento declarado de una clase este se denomina objeto de la clase.

Una vez definida e implementada una clase, es posible declarar o crear elementos de esta clase de modo similar a como se declaran las variables del lenguaje (int, double, char, …).

De una única clase se pueden declarar o crear numerosos objetos. La clase es lo genérico: es el patrón o modelo para crear objetos.

Definición de un Objeto

Sintaxis

         nombreClase nombreObjeto;

nombreClase            Es el nombre de la clase que hemos creado y de la cual queremos crear un objeto o una instancia.

nombreObjeto Cualquier nombre que queramos menos palabras reservadas.

Ahora vamos a crear objetos de nuestra clase gato en la función principal:

void main()

{

gato Tom, Gardfield;

Tom.miau();

Gardfield.miau();

}

Cada objeto tiene sus propias copias de los miembros de la clase, con sus propios valores, en general distintos de los demás objetos de la clase.

Para tener acceso a los miembros de un objeto desde fuera de la clase, se utiliza el punto entre el nombre del objeto y el nombre del miembro, como se muestra en el ejemplo anterior.

Clases Vs Objetos

La relación entre clases y objetos en los programas de computadora es muy similar al  de las variables por ejemplo si tengo

int nota;

         gato Tom;

gato es a Tom como int es a nota, int es un tipo de dato, gato es un tipo de dato definido por el usuario, ambos son tipos de datos, nota es el nombre de la variable, y Tom es el nombre del objeto.

nota es una variable entera en donde se almacenan sólo un dato entero.

Tom es un objeto que puede almacenar varios tipos de datos y además tiene funciones propias de su clase.

Inicialización de Objetos de Clase (Constructores)

Cuando se crea un objeto de alguna manera tenemos que inicializar los valores de sus atributos para que no contengan un valor disparado o queramos fijarlos a  un determinado valor inicial. La inicialización de los atributos lo podemos hacer mediante el constructor de la clase.

El constructor es una función que no tiene tipo y se llama de la misma forma que la clase, y es invocada cada vez que se crea un objeto de la clase automáticamente.

En nuestro ejemplo de la clase gato sería así:

class gato

{

private:

char color[20]; //atributo

int vidas; //atributo

public:

gato(char c[]) //constructor

{

strcpy(color,c);

vidas = 9;

}

void miau() //comportamiento

   {

    cout<<"Miiaaauu!!!";

   }

};

void main()

{

gato Tom("blanco"), Gardfield("negro");

}

Como dijimos que nuestro constructor es una función entonces puede tener parámetros o no, depende de nuestros requerimientos, el constructor de la clase gato recibe un parámetro que es el color, lo que quiere decir que al momento de crear un objeto le daremos el color del que quiero que sea mi gato. Además en el constructor se inicializa el número de vidas en 9, es decir, que cada objeto de la clase gato que se cree tendrá 9 vidas.

Obtener atributos de una Clase

Habíamos mencionado que cuando un atributo es privado no se puede acceder desde fuera de la clase, entonces para poder obtener el valor de este atributo tenemos que crear una función que nos haga este trabajo, y si quisiéramos cambiar este valor también tendríamos que crear una función para este propósito.

Veamos como podemos hacer esto en nuestra clase gato:

class gato

{

private:

char color[20]; //atributo

int vidas; //atributo

public:

gato(char c[]) //constructor

{

strcpy(color,c);

vidas = 9;

}

void miau() //comportamiento

   {

    cout<<"Miiaaauu!!!";

   }

int devVidas() //función miembro

{ return vidas; }

};

void main()

{

gato Tom("blanco"), Gardfield("negro");

cout<<”Tom tiene ”<<Tom.devVidas()<<” vidas”;

}

La función devVidas devuelve el valor de la variable vida que esta encapsulada dentro de la clase gato. Si hubiéramos utilizado en la función principal directamente la siguiente función:

cout<<”Tom tiene ”<<Tom.vidas<<”vidas”;

Existiría un error porque estamos violando los controles de acceso a las variables, vidas es un variable privada. Si vida fuera publica la anterior sentencia estaría correcta.

Modificar atributos de una clase

Para cambiar el valor de un atributo fuera de la clase debemos de construir una función que nos realice este trabajo.

En la clase gato si queremos modificar el valor del atributo vidas será de la siguiente manera:

class gato

{

private:

char color[20]; //atributo

int vidas; //atributo

public:

gato(char c[]) //constructor

{

strcpy(color,c);

vidas = 9;

}

void miau() //comportamiento

   {

    cout<<"Miiaaauu!!!";

   }

int devVidas() //función miembro

{ return vidas; }

void modVidas(int v) //función miembro

{ vidas = v; }

};

void main()

{

gato Tom("blanco"), Gardfield("negro");

cout<<"Tom tiene "<<Tom.devVidas()<<" vidas";

Tom.modVidas( Tom.devVidas() - 1 );

cout<<"Tom tiene "<<Tom.devVidas()<<" vidas";

}

La función modVidas recibe un parámetro que es el nuevo valor que contendrá la variables vidas.

Con la sentencia: Tom.modVidas( Tom.devVidas() - 1 ); se disminuye en 1 el valor del atributo vidas.

Podemos ver que dentro de la función modVidas para acceder a la variable vidas se lo hace directamente, esto es porque lo estoy haciendo desde una función de la misma clase.

Implementación de una función fuera de la clase

Es posible declarar una función dentro de la clase, e implementarla fuera de ella como se muestra a continuación:

 

class gato

{

private:

  char color[20]; //atributo

  int vidas; //atributo

public:

  gato(char[]); //declaración del constructor

  void miau(); //declaración de la función

};

gato::gato(char c[])//implementación del constructor

{

  strcpy(color,c);

  vidas = 9;

}

void gato::miau()//implementación de la función

{

  cout<<"Miiaaauu!!!";

}

Para implementar una función fuera de la clase se debe de declararla tan solo dentro de la clase, y para implementarla fuera de la misma se debe de colocar primero el tipo de la función luego la clase a la que pertenece junto se colocan 4 puntos (::) y el nombre de la función, luego se deben de declarar todos los parámetros que tiene la función. En la declaración de la función dentro de la clase no es necesario darle un nombre a las parámetros basta con el tipo, el nombre se les dará cuando se implemente la función.

Ejemplo 12.1

Simular la pelea de dos gatos, crear dos objetos gato e implementar una función que le permita a un gato arañar a otro gato, cada arañazo que recibe un gato le quita una vida. El programa debe de decidir de forma aleatoria que gato es arañado, todo esto mientras ninguno de los gatos tengan 0 vidas. Al final del programa deberá de decir que gato ganó.

#include <iostream.h>

#include <string.h>

#include <stdlib.h>

class gato

{

private:

char color[20]; //atributo

int vidas; //atributo

public:

gato(char c[]) //constructor

{

strcpy(color,c);

vidas = 9;

}

void miau() //comportamiento

   {

    cout<<"Miiaaauu!!!\n";

   }

int devVidas() //función miembro

{ return vidas; }

void modVidas(int v) //función miembro

{ vidas = v; }

void arania(gato &otroGato)

{

                otroGato.modVidas( otroGato.devVidas() - 1 );

    otroGato.miau();

}

};

void main()

{

gato Tom("blanco"), Gardfield("negro");

int quienArania;

randomize(); //para que se generen nros randomicos distintos

while( Tom.devVidas() > 0 || Gardfield.devVidas() > 0 )

                {

                quienArania = random( 2 );

                if ( quienArania )

                               Tom.arania( Gardfield );

                else

                               Gardfield.arania( Tom );

    }

if(Tom.devVidas() == 0)

                cout<<"Ganó Gardfield";

else

                cout<<"Ganó Tom";

}

Ejemplo 12.2

Construir un tipo de dato que nos permita manejar fracciones, es decir, que este tipo de dato tenga información del numerador y denominador.

Cuando se cree un objeto de esta clase deberá tener valores iniciales de 0, tanto en el numerador como en el denominador, y además debe de poder realizar las siguientes funciones:

         mostrar. Muestra un quebrado

llenar. Llena los datos del quebrado

         sumar. Suma de dos quebrados

         multiplicación. Multiplica dos quebrados

         simplificar. Simplifica un quebrado

#include <iostream.h>

class fraccion

{ private:

            int nume;

            int deno;

  public:

            fraccion() //constructor

            {          nume=0;

                        deno=0;

            }

    //muestra la fracción por pantalla

            void mostrar();

    //llena con datos las variables de la fraccion

            void llenar(int,int);

            //la función sumar recibe como parámetro un tipo de dato fraccion

            // y devuelve el resultado como un tipo de dato fraccion

            fraccion sumar(fraccion);

            //la función multiplicar recibe como parámetro un tipo de dato fraccion

            // y devuelve el resultado como un tipo de dato fraccion.

            fraccion multiplicar(fraccion);

    //simplifica una fraccion

            void simplificar();

    //la funcion euclides calcular el mínimo común divisor de dos nros

    int euclides(int,int);

};

void fraccion::mostrar()

{

            /*

            Si la fraccion tiene denominador 1 se mostrara solo el numerador ó

            Si la fraccion tiene como numerador 0 se mostrara sólo 0

            */

    if((deno == 1) || (nume == 0))

                        cout<<nume<<"\t";

            //Sino se muestra toda la fraccion

            else

                        cout<<nume<<"/"<<deno<<"\t";

}

void fraccion::llenar(int n, int d)

{

            nume=n;

            deno=d;

}

int fraccion::euclides(int nro1, int nro2)

{

            int aux;

            while( nro1 > 0 )

                        {

                                   if(nro1 < nro2)

                                   {

                                               aux = nro1;

                                               nro1 = nro2;

                                               nro2 = aux;

                                   }

                                   nro1 = nro1 - nro2;

                        }

            return nro2;

}

void fraccion::simplificar()

{          int mcd;

            mcd = euclides(nume, deno);

            nume /= mcd;

    deno /= mcd;

}

fraccion fraccion::sumar(fraccion otraFracc)

{

            fraccion aux;

            int mcm = deno * otraFracc.deno;

            aux.nume=(mcm/deno)*nume + (mcm/otraFracc.deno)*otraFracc.nume;

            aux.deno=mcm;

            return aux;

}

fraccion fraccion::multiplicar(fraccion otraFracc)

{

            fraccion aux;

            aux.nume=nume * otraFracc.nume;

            aux.deno=deno * otraFracc.deno;

            return aux;

}

En el anterior ejemplo se muestra como esta implementada la clase fracción, ahora veamos cómo se usa:

void main()

{

fraccion A,B,C;

A.llenar(1,2);

B.llenar(2,2);

A.mostrar();

B.mostrar();

C = A.sumar(B);

C.simplificar();

C.mostrar();

C = A.multiplicar(B);

C.simplificar();

C.mostrar();

}