Java para programadores(4.3). Herencia y Polimorfismo

UNA CLASE REPRESENTA UN CONJUNTO DE OBJETOS que comparten la misma estructura y comportamiento. La clase determina la estructura del objeto al especificar las variables que existirán en cada instancia de la clase, y determina el comportamiento del objeto al proporcionarle los métodos que expresan el comportamiento de estas instancias.Esta es la idea en potencia. Sin embargo, algo como esto, se da en muchos otros lenguajes de programación. La novedad en la idea central de la programación orientada a objetos– la idea que realmente la diferencia de la programación tradicional– es el permitir a las clases que presentes semejanza entre objetos que comparten algo, pero no todo de la estructura y comportamiento. Estas semejanzas pueden expresarse como herencia.

El termino “herencia” se refiere a que una clase puede heredar parte o toda la estructura y comportamiento de otra clase. La clase que recibe la herencia se llama subclase de la que la cede. Si la clase B es una subclase de A, entonces se puede decir que la clase a en una superclase de B.(Aveces se emplean los términos de clase derivada y clase base en lugar fig2 Java para programadores(4.3). Herencia y Polimorfismode subclase y superclase) Una subclase puede  no solo puede añadir a la estructura y comportamiento que hereda, si no que también puede reemplazar o modificar el comportamiento heredado (aunque no la estructura heredada).  Las relaciones entre subclases y superclases se representa a veces por un diagrama en donde las subclases aparecen debajo y conectadas con las superclases.

En Java, cuando crea una nueva clase, puede declarar que es una subclase de una clase existente. Si esta definiendo la clase llamada B y quiere que sea una subclase de la clase llamada A, deberá escribir:

class B extends A {
	.
	.// Adiciones a, y modificaciones de,
	.// la herencia recibida de la clase A

fig3 Java para programadores(4.3). Herencia y Polimorfismo

Es posible declarar varias subclases de una misma superclase. Las subclases, que entonces pueden considerarse “sibling classes” o hermanas, comparten la misma estructura y comportamiento, dado que todas ellas reciben la herencia de una misma superclase. La superclase puede verse entonces, como la expresión de todas las estructuras y comportamientos  iguales de las subclases. En el diagrama de la izquierda, las clases B, C, y D son clases hermanas. La herencia se puede extender a través de varias “generaciones” de clases. Esto se representa en el diagrama donde la clase E es una subclase de la clase D que a su vez es una subclase de la clase A. En este caso, la clase E se considera como una subclase de A, aun cuando la herencia es indirecta.

Vamos a ver un ejemplo. Suponga un programa que trata vehículos de motor, incluyendo coche, camiones y motos (Puede ser un programa usado por el departamento de trafico de una empresa para controlar reparaciones). El programa emplea una clase llamada Vehicle que representa todos los tipos de vehículos. La clase Vehicle puede incluir variables instanciablesfig4 Java para programadores(4.3). Herencia y Polimorfismo como el numero de matricula y el propietario, y métodos instanciables como transferOwnership(). Estas variables y métodos son comunes a todos los vehículos. Podemos usar tres subclases de Vehicle– coches, camiones y motos– que pueden emplearse para guardar variables y métodos específicos de cada tipo de vehículo. La clase coches(Car) puede añadir una variable instanciable para el numero de puertas,numberOfDoors, La clase camión (Truck) puede tener una para el numero de ejes. numberOfAxles, y la clase motos (Motorcycle) puede tener una variable boleana hasSidecar. (Bien, en teoría es posible). La declaración de estas clases en un programa Java, en líneas generales tendría este aspecto:

     class Vehicle {
        int registrationNumber;
        Person owner;  // (asumimos que la clase Person esta definida)
        void transferOwnership(Person newOwner) {
            . . .
        }
        . . .
     }
     class Car extends Vehicle {
        int numberOfDoors;
        . . .
     }
     class Truck extends Vehicle {
        int numberOfAxels;
        . . .
     }
     class Motorcycle extends Vehicle {
        boolean hasSidecar;
        . . .
     }

Supongamos que myCar es una variable de tipo Car que se declara y se inicializa con la instrucción:

Car myCar = new Car();

Entonces será correcto referirse a myCar.numberOfDoors dado que numberOfDoors es una instancia de la variable de la clase Car. Pero como la clase Car extiende la clase Vehicle, también existirán myCar.registrationNumber, myCar.owner, y myCar.transferOwnership.

Ahora, en el mundo real, coches, camiones y motos de hecho, son vehículos. Esto mismo, también es cierto en el programa. Un objeto tipo Car o Truck o Motorcycle es automáticamente un objeto tipo Vehicle. El efecto practico de esto es que a una variable tipo Vehicle se le puede asignar un objeto tipo Car.Ósea que es correcto decir:

Vehicle myVehicle = myCar;

o también

Vehicle myVehicle = new Car();

Después de esta instrucción, la variable myVehicle guarda la referencia a un objeto que casualmente es una instancia de la clase Car. El objeto “recordará” que realmente es Car y no exactamente Vehicle. La información acerca de la clase real, se almacena en el objeto como parte del mismo. O de otra manera, si myVehicle es una variable de tipo Vehicle la instrucción

Car myCar = myVehicle;

será errónea porque myVehicle potencialmente puede referirse a otro tipo de vehículo además de Car.Es un problema similar al que vimos previamente: El ordenador no puede asignar a una variable tipo short otra de tipo int porque no todos los int son short. De forma semejante, no se puede asignar a una variable tipo Vehicle otra de tipo Car porque todos los vehículos son coches. Como en el caso de ints y shorts, la solucion esta en utilizar el tipo   como forma (Car)myVehicle le dice al ordenador que trate myVehicle como si realmente fuera de tipo Car. Entonces, puede decir:

Car myCar = (Car)myVehicle;

y también puede referirse a ((Car)myVehicle).numberOfDoors Tenga en cuenta que para los tipos de objeto, cuando el ordenador ejecuta el programa, comprueba si el tipo de forma es valida. Por ejemplo si myVehicle se refiere a un objeto de tipo Truck,  entonces aplicar la forma (Car)myVehicle provocara un error.


Otro ejemplo, considere un programa que se dedica a dibujar formas en la pantalla. Esto quiere decir que entre las formas incluye rectángulos, elipses y cajas redondas de varios colores.

fig51 Java para programadores(4.3). Herencia y Polimorfismo

 

Para representar los tres tipos de formas, podemos emplear tres clases, Rectangle, Oval, y Roundrect. Estas tres clases pueden tener una superclase común, Shape, para representar las características que las tres clases tienen en común. La clase Shape puede incluir unas variables instanciables para representar el color, posición y tamaño del dibujo. También puede incluir un método  instanciable para cambiar el color, la posición y el tamaño del dibujo. Cambiar el color, por ejemplo, significaría cambiar el valor de la instancia de la variable y redibujar el dibujo con el nuevo color:

       class Shape {

           Color color;   // color del dibujo.  observe que la clase Color
                          // se define en el package java.awt. asuma
                          // que hemos importado la clase.

           void setColor(Color newColor) {
                 // metodo para cambiar el color del dibujo
              color = newColor; // cambiar el valor de la instancia de la variable
              redraw(); // redibujar,con el nuevo color
           }

           void redraw() {
                 // metodo para redibujar el objeto
              ? ? ?  // que comando ponemos?
           }

           . . .          // mas variables y métodos instanciables

       } // end of class Shape

Ahora, puede ver el problema con el método redraw(). El problema es que cada tipo diferente de forma se dibuja diferente. El método setColor() puede ser llamado para cualquier tipo de forma. Como puede saber el ordenador que tipo de forma debe dibujar cuando ejecute redraw()?. Informalmente, podemos contestar a la pregunta de la siguiente forma: El ordenador ejecuta redraw() porque averigua por él mismo la forma que tiene que dibujar. Cada objeto dibujo, sabe como hacer para redibujarse.

En la practica, lo que quiere decir es que cada clase de dibujo tiene su propio metodo redraw():

       class Rectangle extends Shape {
          void redraw() {
             . . .  // instrucciones para dibujar un rectangulo
          }
          . . . // mas métodos y variables
       }
       class Oval extends Shape {
          void redraw() {
             . . .  // instrucciones para dibujar la elipse
          }
          . . . // mas métodos y variables
       }
       class RoundRect extends Shape {
          void redraw() {
             . . .  // instrucciones para dibujar el rectangulo redondo
          }
          . . . // mas métodos y variables
       }

Si oneShape es una variable de tipo Shape, puede referirse a cualquiera de los tipos Rectangle, Oval, o RoundRect. Cuando el programa se va ejecutando, el valor de oneShape cambia, pudiéndose referir a los distintos tipos de objetos, diferentes cada vez!. Entonces cuando se ejecute la instrucción

oneShape.redraw();

el metodo redraw que realmente se llamara, será el apropiado al tipo de objeto que esta siendo guardado en este momento en oneShape.Si de fija en el texto del programa puede que no haya forma de decir  que forma dibujara esta instrucción, dado que depende del valor que tenga oneShape cuando se ejecute el programa. Y hay mas cosas ciertas. Supongamos que la instrucción esta en un bucle que se ejecuta muchas veces. Si el valor de oneShape  va cambiando mientras se ejecuta el bucle, es posible que la misma instrucción “oneShape.redraw();” llame a deferentes métodos y dibuje distintas formas a medida que se va ejecutando una y otra vez.. Decimos entonces que el método redraw() es polimórfico. El polimorfismo es una de las características que mayormente diferencian la programación orientada a objetos.

Quizás todo esto seria mas fácil de entender si cambiamos un poco la terminología: En la programación orientada a objetos, el llamar a un metodo se le dice a menudo enviar un mensaje a un objeto. El objeto responde al mensaje ejecutando el método apropiado. La instrucción “oneShape.redraw();" es el mensaje al objeto referenciado por oneShape. Puesto que el objeto sabe que tipo de objeto es, también sabe como tienen que responder al mensaje. Desde este punto de vista, el ordenador siempre ejecuta  “oneShape.redraw()" de la misma forma: enviando un mensaje. La respuesta al mensaje depende naturalmente de quien lo recibe. Visto así, los objetos son entes activos que envían y reciben mensajes

fig6 Java para programadores(4.3). Herencia y Polimorfismo

y el Poliformismo es una parte de esta visión, natural e incluso necesario. Poliformismo es la posibilidad de que distintos objetos puedan responder al mismo mensaje de diferentes formas

Una de las cosas mas bonitas del Poliformismo, es que permite que el código que escribió pueda hacer cosas que no se le habían ocurrido cuando lo escribió. Si por alguna razón decido que quiero añadir rectangulos biselados  a los tipos de formas de mi programa, puedo escribir una nueva subclase, BeveledRect, de la clase Shape y definir en ella el método redraw(). Automáticamente, el código que había escrito previamente,  tal como la instrucción oneShape.redraw() y la llamada a redraw() dentro del método setColor() pueden de pronto empezar a dibujar rectangulos biselados!.

Volvamos a mirar la clase Shape. Incluye un método redraw(); Debe incluir ese método porque sino, la llamada que se realiza desde el método setColor() al método redraw() seria errónea. Pero, ¿Como podemos definirlo?. La respuesta puede sorprenderle: debe quedar en blanco!. De hecho, la clase Shape representa la idea abstracta de las formas, y no tiene manera de dibujar nada. Solo se pueden dibujar las figuras concretas. En realidad, si lo piensa, vera que no tiene ninguna razón para construir un objeto tipo Shape. Puede tener variables tipo Shape pero siempre se referirán a objetos pertenecientes a una de las subclases de Shape. Decimos entonces que Shape es una clase abstracta. Una clase abstracta es aquella que no se usa para construir objetos y que solo sirve de base para construir subclases. Una clase abstracta existe únicamente para expresar las propiedades comunes a todas las subclases.

De forma semejante, podemos decir que el método redraw() en la clase Shape es un método abstracto, dado que no tienen ningún sentido llamarlo, no sirve para nada; cualquier dibujo se efectúa realmente por medio de uno de los métodos redraw() de alguna de las subclases de Shape. El método redraw() en Shape esta porque tiene que estar. Pero solo sirve para decirle al ordenador que todas las subclases Shapes entienden el mensaje redraw. Como método abstracto, existe meramente para especificar la interface común de todas las versiones concretas y reales del método redraw() en las subclases de Shape. Esta no es razón para que el método abstracto redraw() en la clase Shape tenga que contener código.

Tanto Shape como el método redraw() son semánticamente abstractos. También puede indicarle, sintácticamente,  al ordenador  que son abstractos añadiendo el modificador “abstract” en sus definiciones:

       abstract class Shape {

           Color color;   // color de la figura. 

           void setColor(Color newColor) {
                 // metodo para cambiar el color de la figura
              color = newColor; //cambia el valor de la instancia
              redraw(); // redibuja la figura con el nuevo color
           }

           abstract void redraw();
                 // metodo abstracto redefinido en
                 // las subclases
           . . .          // mas variables y métodos

       } // end of class Shape

 

Una vez ha hecho esto,  es erróneo intentar crear un objeto de tipo Shape y el ordenador le dará un error si lo hace.


En Java cada clase que declara tiene una superclase. Si no especifica la superclase, se toma automáticamente Object que es una clase predefinida que forma parte del package java.lang. (La clase Object no tiene superclase). Entonces,

class myClass {. . .

es exactamente igual a

class myClass extends Object { .  .  .

Todas las demás clases son directa o indirectamente subclases de Object. Esto quiere decir que cualquier objeto que pertenezca a cualquier clase, puede ser asignado a una variable tipo Object. La clase Object representa las propiedades mas generales que comparten todos los objetos, de cualquier clase. Object es la clase mas abstracta de todas!

Acerca de Miguel Garcia

Programador, Desarrollador web, Formador en distintas areas de informatica y director de equipos multidisciplinares.
Esta entrada fue publicada en Formacion, Java y etiquetada , . Guarda el enlace permanente.

Deja un comentario