Archivo

Entradas Etiquetadas ‘objeto’

Polimorfismos: enrevesando la herencia entre clases [C++]

Sábado, 26 de Noviembre de 2011 Gaspar Fernández Sin comentarios

Cuando desarrollamos bajo el paradigma de la programación orientada a objetos, a poco que avanzamos tenemos la sensación de estar escribiendo más para hacer lo mismo que hacíamos antes sin clases ni instancias ni tanta estructuración.
Pero contamos con herramientas que, aunque al principio nos sonarán algo chocantes, a la larga terminarán abriéndonos un mundo de posibilidades y comprenderemos por qué es tan importante, tan utilizado y se exige en multitud de universidades y centros educativos.
Uno de esos grandes avances que nos permite la programación orientada a objetos y que hoy particularizaremos a C++ son los polimorfismos.

Imaginemos (el ejemplo es clases_animalun poco tonto, lo reconozco, pero es lo más sencillo que se me ocurre para no llenar el post de código. Estoy abierto a sugerencias) que vamos a crear un software de mascota virtual, para eso construimos una clase padre (o superclase) llamada Animal. El sistema está en una fase muy temprana de desarrollo y los animales sólo pueden emitir sonidos (aunque en este programa se pondrá una onomatopeya por pantalla).

A partir de la clase Animal crearemos las clases Perro y Gato. Lo que necesitamos es que todos los miembros de Animal tengan un método hablar() que les permita emitir su sonido característico. Nuestra función main() queda así:

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
  Mascota *p, *g;
 
  p=new Perro("Ramiro");
  g=new Gato("Lancelot");

  cout << p->hablar() << endl;
  cout << g->hablar() << endl;

  return 0;
}

Lo que deseamos es que p (declarado como Mascota, pero en realidad es de clase Perro), diga “GUAU” y g (instancia de Gato) diga “MIAU”. Para eso necesitamos crear un polimorfismo (será un método que en cada subclase (clase heredera, o clase hija) ejecute el método correspondiente dependiendo de la clase de la instancia que tengamos. En C++ lo hacemos con métodos virtuales:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
#include <string>

using namespace std;

class Mascota
{
public:
  Mascota(string nombre, int patas);
  virtual string hablar() = 0;

private:
  string nombre;
  int patas;
};

Mascota::Mascota(string nombre, int patas):nombre(nombre),patas(patas)
{
  cout << "Acaba de nacer tu mascota "<<nombre<<" con "<<patas<<"patas"<<endl;
}

class Perro : public  Mascota
{
public:
  Perro(string nombre);

  string hablar();
};

Perro::Perro(string nombre):Mascota(nombre, 4)
{
}

string Perro::hablar()
{
  return "GUAU";
}

class Gato : public  Mascota
{
public:
  Gato(string nombre);

  string hablar();
};

Gato::Gato(string nombre):Mascota(nombre, 4)
{
}

string Gato::hablar()
{
  return "MIAU";
}

Cuando lo ejecutemos, una vez creados el Perro (Ramiro) y el Gato (Lancelot), cada uno dirá su palabra correspondiente. Para entender la diferencia de lo que origina utilizar virtual o no utilizarlo, lo mejor es probarlo. Vemos que para crear la función “virtual string hablar()” escribimos =0 (es una forma especial para que la función quede definida y no sólo declarada). Por tanto, para eliminar el virtual, debemos declarar la función como virtual string hablar() y definir su comportamiento. El fragmento quedaría así:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Mascota
{
public:
  Mascota(string nombre, int patas);
  string hablar();

private:
  string nombre;
  int patas;
};

Mascota::Mascota(string nombre, int patas):nombre(nombre),patas(patas)
{
  cout << "Acaba de nacer tu mascota "<<nombre<<" con "<<patas<<"patas"<<endl;
}

string Mascota::hablar()
{
  return "BUU";
}

En este caso, un “Animal por defecto” (que no pertenecería a ninguna especie) diría “BUU”, si ejecutamos el programa tal cual lo tenemos ahora, la salida sería:

Acaba de nacer tu mascota Ramiro con 4patas
Acaba de nacer tu mascota Lancelot con 4patas
BUU
BUU

¡ Los dos dirían BUU ! Y eso es porque inicialmente están declaradas como Animal, así que al llamar al método hablar() se llamará al de la clase Animal en lugar de Perro o Gato.

Ahora hacemos una pequeña modificación. Cambiamos los métodos virtual hablar() por palabra() y los ponemos en la parte privada, para el acceso desde main() creamos otra función hablar() no virtual en Mascota, cuyo contenido será:

1
2
3
4
string Mascota::hablar()
{
  return nombre+" dice: "+this->palabra();
}

El objetivo es que cada animal, antes de decir su palabra escriba: “(minombre) dice: (mipalabra)”, en el caso del perro: “Ramiro dice: GUAU”. Aunque claro, es un método de Animal, por tanto this apuntaría a un Animal, que lo es, pero también es un Perro y como perro tiene su palabra característica y su función palabra() definida y propia, en la parte privada, por lo que intuitivamente no tendríamos acceso; pero virtual también funciona aquí para poder seleccionar el método hablar() de la clase correspondiente.

El resultado final queda así (por si quieres copiar y pegar):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <iostream>
#include <string>

using namespace std;

class Mascota
{
public:
  Mascota(string nombre, int patas);
  string hablar();

private:
  virtual string palabra();

  string nombre;
  int patas;
};

Mascota::Mascota(string nombre, int patas):nombre(nombre),patas(patas)
{
  cout << "Acaba de nacer tu mascota "<<nombre<<" con "<<patas<<"patas"<<endl;
}

string Mascota::palabra()
{
  return "BUUU";
}

string Mascota::hablar()
{
  return nombre+" dice: "+this->palabra();
}

class Perro : public  Mascota
{
public:
  Perro(string nombre);

  string palabra();
};

Perro::Perro(string nombre):Mascota(nombre, 4)
{
}

string Perro::palabra()
{
  return "GUAU";
}

class Gato : public  Mascota
{
public:
  Gato(string nombre);

  string palabra();
};

Gato::Gato(string nombre):Mascota(nombre, 4)
{
}

string Gato::palabra()
{
  return "MIAU";
}

int main()
{
  Mascota *m, *g;
 
  m=new Perro("Ramiro");
  g=new Gato("Lancelot");

  cout << m->hablar() << endl;
  cout << g->hablar() << endl;
  return 0;
}

Nota: A veces también se llama polimorfismo a la sobrecarga (de métodos, por ejemplo, donde podemos describir varios métodos homónimos (todos llevarán el mismo nombre), pero con diferentes tipos de argumento, y en función de ellos, el compilador podrá saber a qué método llamamos. Todo esto tiene como función hacer la programación algo más intuitiva y natural.

Curioso e interesante: Google se derrumba, K-Maps, Buenhabit, y más

Jueves, 3 de Junio de 2010 Gaspar Fernández 2 comentarios

Quiero dedicar este post a cosas que me han llamado la atención estos últimos días:

  1. Google se derrumba: Lo descubrí gracias a novatillasku. Es una tontería chulísima, la página principal de Google se cae a pedazos, aunque para verlo tendréis que utilizar un navegador decente. Abstenerse Internet Explorer. Click aqui para verlo.
  2. Mapas de Karnaugh: Via Acerca de Ubuntu veo una serie de programas para resolver mapas de Karnaugh y ser el terror de la electrónica digital :)
  3. Piedra, papel, tijeras, lagarto, Spock: Últimamente estoy viendo la serie The Big Bang Theory, ya estaba tardando en empezarla, y hace poco vi este post. Pocos días antes de llegar hasta el capítulo 2×08 donde ocurre todo :)
  4. Acceder a un Array como un objeto [PHP]: El post ya tiene tiempo. Leído en el blog de Sergi Quiñonero. Nos cuenta cómo haciendo $objeto = (object) $array podemos acceder a cada elemento del array con -> (flechas) como si fuera un objeto.
  5. El sistema Solar en HTML5. Muy currado, demostrando qué es capaz de hacer HTML5, y demasiado cool para IE, aunque está gracioso ver cómo se dibujan en Internet Explorer las órbitas cuadradas :) Ya sé que tampoco son redondas, pero es una demostración sólo…
  6. Dos artículos de un blog muy interesante: Hace mucho tiempo que sigo el blog Buenhabit, y quiero destacar dos de sus últimos posts: Sólo dos cosas a la vez, que habla de una reciente investigación acerca de la limitación humana de la multitarea; y Si gritas no convences, merece la pena leerlo aunque el título lo diga todo.

Hasta aquí una pequeña selección de estos últimos días.

Getters / Setters en PHP

Sábado, 1 de Mayo de 2010 Gaspar Fernández Sin comentarios

Cuando estamos programando con clases en PHP, a veces tenemos la necesidad de acceder a atributos privados de una clase desde fuera, tal vez para sólo lectura, sólo escritura, o porque estos no pueden tener cualquier valor.

Pero claro, son atributos privados, no podemos hacer esto tal alegremente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class test
{
    private $privado;
    function test()
    {
      $privado="Variable privada";
    }  
}

$t=new test;

echo $t->privado;

?>

En C++, por ejemplo, definimos getters/setters para todos los atributos qud podamos “tocar” desde fuera, aunque, es escribir demasiado (tratamos cada atributo por separado, y está bien). Por otra parte, en PHP tenemos los métodos mágicos, y entre ellos encontramos __get() y __set() y haciendo caso al ejemplo anterior podemos hacer lo siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class test
{
    private $privado;
    function test()
    {
      $this->privado="Variable privada";
    }  
   
    function __get($atrib)
    {
      return $this->$atrib;
    }
}

$t=new test;

echo $t->privado;
?>

Aunque de esta forma, para qué queremos atributos privados ? Si bueno, podríamos leerlos todos, escribirlos no, aunque podemos hacer una pequeña implementación para tener un cierto control sobre qué atributos privados son visibles desde fuera:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
class test
{
    private $privado;
    private $visible;
    private $oculto;
    private $atributos_visibles=array("privado", "visible");
    function test()
    {
      $this->privado="Variable privada";
      $this->visible="Atributo privado visible";
      $this->oculto="Atributo privado oculto";
    }  
   
    function __get($atrib)
    {
      if (in_array($atrib, $this->atributos_visibles))
        return $this->$atrib;
      else
        return false;
    }
}

$t=new test;

echo $t->privado."\n";
echo $t->visible."\n";
echo $t->oculto."\n";
?>

Obtendremos:

$ php getset.php
Variable privadaAtributo privado visible[gaspy@lytio blog]$ php getset.php
Variable privada
Atributo privado visible

De esta forma, podemos evitar errores en caso de atributos inexistentes, y evitar que los que no queremos no se puedan leer. Lo mismo podemos hacer con los setters:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php
class test
{
    private $privado;
    private $visible;
    private $oculto;
    private $atributos_visibles=array("privado", "visible");
    private $atributos_tocables=array("privado");
    function test()
    {
      $this->privado="Variable privada";
      $this->visible="Atributo privado visible";
      $this->oculto="Atributo privado oculto";
    }  
   
    function __get($atrib)
    {
      if (in_array($atrib, $this->atributos_visibles))
        return $this->$atrib;
      else
        return false;
    }

    function __set($atrib, $valor)
    {
      if (in_array($atrib, $this->atributos_tocables))
        $this->$atrib=$valor;
    }
}

$t=new test;

$t->privado="VALOR NUEVO DE PRIVADO";
$t->visible="VALOR NUEVO DE VISIBLE";
echo $t->privado."\n";
echo $t->visible."\n";
echo $t->oculto."\n";
?>

Por otra parte, si deseamos filtrar la entrada de esos atributos, es decir, que por ejemplo, privado deba ser un número entre 0 y 10, debemos implementarlo dentro de __set (con un switch por ejemplo, o comprobar si es un valor que debemos filtrar, entonces llamamos a una función que verifique su valor).

Visita otras webs de la red