Ayer mismo os hablaba del polimorfismo en POO y como lo implementa Java. Espero que el post fuese ilustrador, pero con él pretendía asentar las bases para hablar del Enlace dinámico en Java. Permitidme que os aclare que en la lengua de Shakespeare esto se llama Dynamic Binding. Si cometéis el mismo error que yo y pretendéis buscar información sobre el tema usando las kewywords Dynamic Linking estaréis leyendo sobre un tema muy distinto.
Aclarado esto, vamos al grano…
¿Qué es el enlace dinamico en Java?
Si recordáis el post anterior, cuando hablabamos del polimorfismo de asignación, os decía que cualquier variable tiene un tipo estático y un tipo dinámico. Esto significa que una misma variable tiene dos tipos: un tipo que se interpreta en tiempo de ejecución (tipo dinámico) y un tipo que se interpreta en tiempo de compilación (tipo estático). Se que esta definición no aclarará mucho, así que vamos a intentar entenderlo paso a paso y con ejemplos. Al acabar, espero que la definición la entendáis mucho mejor.
Empecemos poniendo la base de nuestro pequeño ejemplo. Supongamos que tenemos estas clases:
abstract class DriverBD { abstract public boolean conectar(String host, int puerto, String bd); abstract public boolean desconectar(); abstract public void ejecutarSentencia(String sentencia); public void mostrarVersionDriver() { System.out.println("Driver genérico de ejemplo, versión 1.0."); } } public final class DriverMySql extends DriverBD { @Override public boolean conectar(String host, int puerto, String bd) { return false; } @Override public boolean desconectar() { return false; } @Override public void ejecutarSentencia(String sentencia) { } @Override public void mostrarVersionDriver() { System.out.println("Driver de MySQL Versión 1.0"); } }
El ejemplo es bastante sencillo. Una clase DriverBD de la que heredarán muchas clases distintas encargadas de gestionar distintas bases de datos. Antes de nada, aclarar que este ejemplo no es una buena implementación de un sistema de bases de datos, sencillamente lo hago porque estoy cansado de que tengamos que recurrir siempre a ejemplos tipo Persona, Animal, Mamifero, etc para este tipo de cosas que creo que no ayudan a un programador a verle usos a cosas como polimorfismo, herencia, interfaces, etcétera.
Una vez hayáis entendido que este código no es bueno para otra cosa que no sea explicar cosas sencillas, vamos al grano.
Cuando hablábamos del polimorfismo de asignación, comentábamos que podíamos hacer esto:
DriverBD driver = new DriverMySql();
Como véis, declaramos una variable de tipo «DriverBD» para inicializarla con una clase de tipo «DriverMySql». Esto es posible gracias al polimorfismo de asignación, que nos permite asignar a una variable cualquier clase que tenga una relación de herencia o implementación válida con la misma. Podemos decir que el tipo estático de una variable es el tipo con el que se ha declarado. Así de fácil. El tipo estático de la variable «driver» es «DriverBD», porque es el que hemos usado en la declaración. Como lo hemos declarado explícitamente, la JVM sabe, desde tiempo de compilación, el tipo estático de una variable.
Ahora la JVM sabe que en la variable «driver» va a almacenar cualquier clase compatible con la clase «DriverBD». Pero, ¿como sabe la JVM de qué tipo es cuando un mismo tipo estático puede albergar un número indefinido de clases?, la respuesta es: conociendo su tipo dinámico.
¿Qué es el tipo dinámico?, pues el tipo dinámico es la clase «real» que alberga la variable «driver». En el ejemplo de código, la variable está albergando una clase «DriverMySql», no una tipo «DriverBD». En resumidas cuentas, el tipo dinámico de una variable es el tipo con el que se ha inicializado dicha variable.
Hasta ahora, sabemos lo siguiente:
- La variable «driver» se ha declarado como una clase de tipo «DriverBD».
- El tipo estático de la variable «driver» es «DriverBD», porque es el valor con el que la hemos declarado.
- Hemos almacenado una variable tipo «DriverMySql» en la variable al hacer driver = new DriverMySql();
- El tipo dinámico de la variable es «DriverMySql«, porque es el valor con el que la hemos inicializado.
¿Pero para qué sirve el enlace dinámico?
Pues sirve para hacer programas más robustos y flexibles. Siguiendo con los anteriores ejemplos, podríamos hacer una clase que se encargase de gestionar la base de datos en función de un driver dado. Sin importarnos cual sea. Sin que haya problemas si un día decidimos pasar de SQL Server a MySQL o cualquier otro SGBD.
Fijaros en este ejemplo de dicha clase:
final class AccesoBD { private DriverBD driver; public AccesoBD(DriverBD driver) { this.driver = driver; } public void setDriver(DriverBD driver) { this.driver = driver; } public boolean conectar(String host, int puerto, String bd) { return this.driver.conectar(host, puerto, bd); } public boolean desconectar() { return this.driver.desconectar(); } public void ejecutarSentencia(String sentencia) { this.ejecutarSentencia(sentencia); } }
Esta clase se encarga de que nuestra aplicación trate con una base de datos, independientemente del tipo de base de datos que sea. Cada SGBD tiene sus peculiaridades que serán tratadas por los distintos drivers, pero para la clase AccesoBD la implementación interna de cada clase le da igual. Veamos un ejemplo de como usaríamos esta clase.
AccesoBD BD = new AccesoBD(new DriverMySql()); bd.conectar("localhost", 3306, "bd_pruebas"); bd.ejecutarSentencia("CREATE TABLE test;"); bd.setDriver(new DriverSQLServer()); bd.conectar("localhost", 1433, "test"); bd.ejecutarSentencia("CREATE TABLE test;");
Las capas de abstracción unidas al enlace estático/dinámico nos permiten tener un sistema de bases de datos completamente modular. Os recuerdo que estos ejemplos son una mala solución para este problema. Otro día os mostraré distintos enfoques correctos a esta problemática, pero ahora está aquí para cumplir la misión de dar ejemplos sin caer en tópicos de animales, vehículos, etcétera.
Gracias al enlace dinámico podemos decirle a la JVM: «te va a hacer falta un driver que va a heredar de DriverBD, pero no se exactamente cual será, eso tendrás que averiguarlo en tiempo de ejecución«. De esta forma, la aplicación podrá tratar con cualquier driver sin que tengamos que especificar exactamente cual. Con esto ganamos en robustez (podemos especificar que clase de objeto va a recibir la función y evitar llamadas con objetos incorrectos) y flexibilidad (no tenemos que ser explícitos en esta declaración, podemos usar superclases o interfaces).
Confío en que hayáis entendido lo bueno. Ahora vamos a hablar de lo malo.
¿Qué limitaciones tiene el enlace dinamico en Java?
Una de cal y otra de arena. Al trabajar con variables cuyo tipo dinámico sea distinto a su tipo estático, solo podremos usar métodos que estén definidos en el tipo estático.
Esto significa que si definimos un método en una subclase que no haya sido definido previamente en la superclase (o interfaz), no podremos hacer una llamada a dicho método en una variable cuyo tipo estático sea distinto.
Vamos a ver un ejemplo de que significa esto.
Imaginad que hacemos esta clase (que usamos anteriormente, pero no definí porque me la reservaba):
public final class DriverSQLServer extends DriverBD { @Override public boolean conectar(String host, int puerto, String bd) { return false; } @Override public boolean desconectar() { return false; } @Override public void ejecutarSentencia(String sentencia) { } public void ejemploMetodoUnico() { System.out.println("Este método es único a la clase, no lo tenemos definido en la superclase."); } @Override public void mostrarVersionDriver() { System.out.println("Driver de SQL Server 2008 Versión 1.0"); } }
La clase sigue el espíritu de los drivers anteriormente ejemplificados, pero añade el método ejemploMetodoUnico() que usaré para mostrar la limitación del enlace dinámico.
Veamos el siguiente código. Antes de seguir adelante intenta averiguar que resultado dará.
DriverSQLServer a = new DriverSQLServer(); DriverBD b; /* * Polimorfismo de asignación: la declaración de B es 'driverbd' pero * la inicializamos como 'DriverSQLServer' * * Podemos decir, por tanto, que 'DriverBD' es el tipo estático y * 'DriverSQLServer' el tipo dinámico */ b = new DriverSQLServer(); // ¿Qué resultado darán cada una de esta sinvocaciones? a.ejemploMetodoUnico(); b.ejemploMetodoUnico();
¿Qué creéis que pasará al ejecutar ejemploMetodoUnico() en a y b?
- Al hacerlo en a no pasará nada raro, pues el tipo dinámico de la variable es igual al tipo estático (DriverSQLServer). El método se ejecuta con éxito.
- Al hacerlo en b, obtendremos una excepción, puesto que el tipo estático no tiene definido el método ejemploMetodoUnico().
Así pues, repetimos esta limitación: no podemos hacer una llamada a un método definido en un tipo dinámico que no esté, a su vez, definido en el tipo estático.
¿Como se solucionaría esto? pues asignando el valor de B a una variable de tipo estático válido y haciendo el casting correspondiente.
DriverSQLServer a; DriverBD b = new DriverSQLServer(); a = (DriverSQLServer) b; // Asignamos B (tipo estático DriverBD) a una variable de tipo estático (DriverSQLServer) a.ejemploMetodoUnico(); // Al ser iguales tipo estático y dinámico no tenemos problema
Confío en que con esto os haya quedado todo bastante más claro. Otro día os hablare del enlace dinámico en super() y this() cuando estamos en relaciones de herencia complejas.
Mientras tanto, os dejo con alguna lectura relacionada:
Eliel
En el tema anterior no me permitía dejar comentarios, al parecer esta mal el captcha aunque uno ponga la respuesta correcta. De todos modos gracias por la info
Héctor Luaces Novo
¡Gracias!, echaré un vistazo a lo del Captcha.